React

리액트로 회원가입 구현하기 - useRef vs useState

에어팟맥스 2023. 5. 8. 14:37

전체 코드

import { Box, Button, Checkbox, CssBaseline, FormControlLabel, Grid, TextField, Typography, } from "@mui/material";
import { Link, useNavigate } from "react-router-dom";
import Container from "@mui/material/Container";
import { useRef } from "react";
import { LocalStorageController } from "../../util/LocalStorageController";
import { FormDataType } from "./type/FormDataType.type";
import React from "react";

export const Register = () => {
  const navigate = useNavigate();

// is~~~Valid : input 값의 유효성을 검사하는 상태값. 
// 현재는 ""(빈칸)이 아니어야 통과를 할 수 있게 만들었지만 나중에 정규식을 넣어서 검사하도록 하려고 한다.
// 값이 false 이면 html 코드에서 error={!isUserNameValid}, helperText={isUserNameValid ? "" : "필수 입력 항목입니다."}
// 로 경고창이 뜨게 된다.
  const isUserNameValid = useRef(false);
  const isUserIdValid = useRef(false);
  const isPasswordValid = useRef(false);
  const isPasswordCheckValid = useRef(false);

// input 의 value 값
  const userName = useRef("");
  const userId = useRef("");
  const password = useRef("");
  const passwordCheck = useRef("");

  const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  // input이 변할때마다 input의 name별로 value와 유효성 검사상태를 업데이트 해줌.
  
    const { name, value } = event.target;
    switch (name) { 
      case "userName":
        userName.current = value;
        isUserNameValid.current = Boolean(value);
        break;
      case "userId":
        userId.current = value;
        isUserIdValid.current = Boolean(value);
        break;
      case "password":
        password.current = value;
        isPasswordValid.current = Boolean(value);
        break;
      case "passwordCheck":
        passwordCheck.current = value;
        break;
      default:
        break;
    }

// 비밀번호 input 입력창과 비밀번호 확인의 input 입력창의 value 가 같다면 true 반환
    if (passwordCheck === password) {
      isPasswordCheckValid.current = true;
    } else {
      isPasswordCheckValid.current = false;
    }
  };

// 확인 버튼을 누르면 유효성을 체크하고 input의 입력값들(사용자 정보)을 로컬스토리지에 저장해주고
// 로그인 화면으로 이동시켜줌
  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();

    if (
      isUserNameValid &&
      isUserIdValid &&
      isPasswordValid &&
      isPasswordCheckValid
    ) {
      let n_id: number = 0;
      let point: string = "50000";

      let userData: FormDataType = {
        id: n_id.toString(),
        userName: userName.current,
        userId: userId.current,
        password: password.current,
        userPoint: point,
      };

      let userList: FormDataType[] =
        LocalStorageController.getLocalStorageList<FormDataType[]>(
          "userList"
        ) ?? [];

      if (userList.length > 0) {
        n_id = parseInt(userList[userList.length - 1].id) + 1;
        userData.id = n_id.toString();
      }

      userList.push(userData);
      LocalStorageController.saveLocalStorage<FormDataType[]>(
        "userList",
        userList
      );
      alert("회원가입이 완료되었습니다.");
      navigate("/login");
    }
  };

  return (
    <Container component="main" maxWidth="xs">
      <CssBaseline />
      <Box
        sx={{
          marginTop: 22,
          display: "flex",
          flexDirection: "column",
          alignItems: "center",
        }}
      >
        <Typography component="h1" variant="h5">
          회원가입
        </Typography>
        <Box component="form" noValidate onSubmit={handleSubmit} sx={{ mt: 3 }}>
          <Grid container spacing={2}>
            <Grid item xs={12}>
              <TextField
                name="userName"
                required
                fullWidth
                id="userName"
                label="성명"
                autoFocus
                onChange={handleInputChange}
                error={!isUserNameValid}
                helperText={isUserNameValid ? "" : "필수 입력 항목입니다."}
              />
            </Grid>
            <Grid item xs={12} container spacing={2} alignItems="center">
              <Grid item xs={9}>
                <TextField
                  required
                  fullWidth
                  id="userId"
                  label="아이디"
                  name="userId"
                  onChange={handleInputChange}
                  error={!isUserIdValid}
                  helperText={isUserIdValid ? "" : "필수 입력 항목입니다."}
                />
              </Grid>
              <Grid item xs={3}>
                <Button variant="contained">중복확인</Button>
              </Grid>
            </Grid>
            <Grid item xs={12}>
              <TextField
                required
                fullWidth
                name="password"
                label="비밀번호"
                type="password"
                id="password"
                onChange={handleInputChange}
                error={!isPasswordValid}
                helperText={isPasswordValid ? "" : "필수 입력 항목입니다."}
              />
            </Grid>
            <Grid item xs={12}>
              <TextField
                required
                fullWidth
                name="passwordCheck"
                label="비밀번호 확인"
                type="password"
                id="passwordCheck"
                onChange={handleInputChange}
                error={!isPasswordCheckValid}
                helperText={isPasswordCheckValid ? "" : "필수 입력 항목입니다."}
              />
            </Grid>
            <Grid item xs={12}>
              <FormControlLabel
                control={<Checkbox value="allowExtraEmails" color="primary" />}
                label="약관 동의"
              />
            </Grid>
          </Grid>
          <Button
            type="submit"
            fullWidth
            variant="contained"
            sx={{ mt: 3, mb: 2 }}
          >
            확인
          </Button>
          <Grid container justifyContent="flex-end">
            <Grid item>
              {<Link to="/login">이미 계정이 있다면?! 로그인 하러 가기</Link>}
            </Grid>
          </Grid>
        </Box>
      </Box>
    </Container>
  );
};

html 코드는 Material UI 에서 제공하는 free template - sign up 사용(https://mui.com/material-ui/getting-started/templates/)


 
useState와 useRef 는 모두 리액트에서 상태를 관리하는데 사용되는 Hook이다. 하지만 둘은 목적과 사용 방법에서 차이가 있다.
 

useState()

 - useState는 컴포넌트 내부에서 변화하는 값을 저장하고, 이 값을 변경할 때마다 컴포넌트를 리렌더링한다.
 - 상태를 변경할 때는 setState() 함수를 사용하며, 상태를 변경하면 해당 컴포넌트와 하위 컴포넌트가 모두 리렌더링된다.   - 상태는 컴포넌트에서 변경되는 값이며, 다른 컴포넌트와 공유되지 않는다.
 

useRef()

 - useRef는 DOM 요소에 직접 접근하거나, 특정 값을 기억하고 싶을 때 사용된다.
 - useRef로 생성된 객체는 컴포넌트가 리렌더링되어도 계속 유지된다. 
 - useRef는 일반적으로 상태 변경에 따른 리렌더링과는 상관없이 동작하는 로직을 구현할 때 사용된다.
 

useState와 useRef 의 차이점

 - useState는 상태를 변경할 때마다 리렌더링이 발생하며, useRef는 상태 변경과 관계없이 계속 유지된다.
 - useState는 상태 값을 변경할 때마다 새로운 상태를 생성하는 반면 useRef는 기존에 생성된 객체를 계속 사용한다.
 - useState는 상태 값이 변경될 때마다 컴포넌트와 하위 컴포넌트가 모두 리렌더링되지만 useRef는 상태 변경과 관계없이 컴포넌트가 리렌더링되어도 기존 값을 유지한다.
 - useState는 컴포넌트 내에서 값을 저장하는 용도로 주로 사용되는 반면 useRef는 DOM 요소에 직접 접근하거나, 이전 값을 기억해야 하는 경우에 주로 사용된다.
 


 
이 회원가입 기능을 구현하면서 처음에는 useState 로 아래처럼 초기값을 설정하고 사용했는데, 이렇게 하면 input 값이 바뀔 때마다 컴포넌트가 리렌더링이 된다.

  const [isUserNameValid, setIsUserNameValid] = useState(false);
  const [isUserIdValid, setIsUserIdValid] = useState(false);
  const [isPasswordValid, setIsPasswordValid] = useState(false);
  const [isPasswordCheckValid, setIsPasswordCheckValid] = useState(false);

  const [userName, setUserName] = useState("");
  const [userId, setUserId] = useState("");
  const [password, setPassword] = useState("");
  const [passwordCheck, setPasswordCheck] = useState("");

 

export const Register = () => {
	console.log("rerendering");

이렇게 콘솔로 찍어보면 useState를 사용했을 때는 input 값이 바뀔 때마다 콘솔창에 아래처럼 rerendering 이 계속 찍히는데( = 컴포넌트가 리렌더링 되고 있다는 것)

 
useRef 로 바꾸면 아래처럼 처음 렌더링 되었을 때만 콘솔창에 찍히고 input 에 값을 입력해도 아무것도 뜨지 않게 된다.

 
따라서 지금같은 회원가입창의 경우 input 값이 바뀔 때마다 리렌더링이 되는 것은 불필요하기 때문에 useState를 사용하지 않고 useRef 로 바꾸어 주었다.
 
지금처럼 작은 규모에서는 상관이 없겠지만 사이트의 규모가 커지게 되고, 컴포넌트 트리가 깊거나 복잡해지는 경우에는 불필요한 리렌더링이 발생하여 성능 저하의 원인이 될 수도 있다.
또한, 상태 값이 변경되어도 렌더링이 필요 없는 경우에도 리렌더링이 발생하여 낭비가 될 수 있다.
결론적으로 useState를 사용할 때에는 상태 값이 변경될 때 컴포넌트를 리렌더링해야 하는지에 대해 신중하게 고려해야 한다.
 
 

'React' 카테고리의 다른 글

리액트 useContext  (0) 2023.05.09