develop

react로 회원가입(1)-커스텀훅,구조분해할당,useCallback

crab. 2023. 1. 20. 16:08

회원가입 기능

  • 웹 서비스를 만든다면 제일 처음에 만드는 기능은 회원가입이지 않을까 싶다.
  • 다양한 회원가입 형식들이 있지만 이번에 내가 사용한 방식을 작성해보고자 한다.
  • 우선 기입사항은 아이디(이메일), 비밀번호, 비밀번호 확인 이렇게 3가지의 기본형태이다.

커스텀 hook

  • 회원가입과 로그인에 사용되는 input은 꽤나 자주 같은 방식으로 사용되기에
  • useInput이라는 hook을 만들어 관리하면 편하다.
  • 여기서 크게 짚고 넘어갈게 두가지 있는데
  • 하나는 구조분해할당, 다른 하나는 useCallback이다.

구조분해할당

  • 구조분해할당은 객체나 배열을 변수로 ‘분해’할 수 있게 해주는 특별한 문법이다.
  • 따라서 사용할때 const [id, onChangeId] = useInput(""); 을 하면
  • useInput은 반환값으로 value,handler,setter를 반환하고(여기서 지금당장은 setter가 쓰이지 않는다.)
  • 이때 value와 handler는 각각 useState의 상태값과 useCallback의 함수인데(useState의 set함수가 들어있는)
  • 우리는 이 value와 handler를 그대로 사용하면된다.
  • 사용할때는 value가 id이고 onChange가 onChangeId이다.
  • 이렇게 하면 input값이 바뀔때마다 그 안의 value값도 바뀌게 된다.
  • 그럼 그냥 useState를 쓰면 되지 왜 이렇게 하는 걸까?
import { useState, useCallback } from "react";

// eslint-disable-next-line import/no-anonymous-default-export
export default (initValue = null) => {
  const [value, setter] = useState(initValue);
  const handler = useCallback(e => {
    setter(e.target.value);
  }, []);
  return [value, handler, setter];
};

////////////////////////////////////////////////////////////////
//in Singup.jsx 에서 사용함 위와 다른 컴포넌트//
const [id, onChangeId] = useInput("");

////////////////////////////////////////////////////////////////
//in Singup.jsx 에서 사용함 위와 다른 컴포넌트//
								<input
                  ref={idRef}
                  type="id"
                  value={id}
                  onChange={onChangeId}
                  placeholder="아이디(이메일)"
                  onBlur={onClickIdDup}
                  data-testid="idInput"
                />

useCallback

  • useCallback은 중요하다.
  • 이 useCallback을 언제, 왜 사용하는 지 알려면 우선 자바스크립트의 함수 동등성을 알아야한다.
> const add1 = () => x + y;
undefined
> const add2 = () => x + y;
undefined
> add1 === add2
false
  • 자바스크립트에서는 함수도 객체로 취급이 되기 때문에 메모리 주소에 의한 참조 비교가 일어난다.
  • 이러한 자바스크립트의 특성은 React 컴포넌트 함수 내에서 어떤 함수를 다른 함수의 인자로 넘기거나 자식 컴포넌트의 prop으로 넘길 때 예상치 못한 성능 문제로 이어질 수 있다.
  • 예를 들어, 다음과 컴포넌트에서 API를 호출하는 코드는 fetchUser 함수가 변경될 때만 호출된다.
  • 여기서 위에서 설명드린 자바스크립트가 함수의 동등성을 판단하는 방식 때문에 예상치 못한 무한 루프에 발생하게 된다.
  • fetchUser는 함수이기 때문에, userId 값이 바뀌든 말든 컴포넌트가 랜더링될 때 마다 새로운 참조값으로 변경이 된다.
  • 그러면 useEffect() 함수가 호출되어 user 상태값이 바뀌고 그러면 다시 랜더링이 되고 그럼 또 다시 useEffect() 함수가 호출되는 악순환이 반복된다.
import React, { useState, useEffect } from "react";

function Profile({ userId }) {
  const [user, setUser] = useState(null);

  const fetchUser = () =>
    fetch(`https://your-api.com/users/${userId}`)
      .then((response) => response.json())
      .then(({ user }) => user);

  useEffect(() => {
    fetchUser().then((user) => setUser(user));
  }, [fetchUser]);

  // ...
}
  • 이와 같은 상황에서 useCallback()hook 함수를 이용하면 컴포넌트가 다시 랜더링되더라도 fetchUser함수의 참조값을 동일하게 유지시킬 수 있다.
  • 따라서 의도했던 대로, useEffect()에 넘어온 함수는 userId값이 변경되지 않는 한 재호출 되지 않게 된다.
import React, { useState, useEffect } from "react";

function Profile({ userId }) {
  const [user, setUser] = useState(null);

  const fetchUser = useCallback(
    () =>
      fetch(`https://your-api.com/users/${userId}`)
        .then((response) => response.json())
        .then(({ user }) => user),
    [userId]
  );

  useEffect(() => {
    fetchUser().then((user) => setUser(user));
  }, [fetchUser]);

  // ...
}
  • 그럼 이제 궁금한 건 우리의 함수에 어떻게, 왜 적용했냐이다.
  • 우리의 hook에는 의존성배열이 []인데, 이렇게 하면 최초 렌더링시에만 함수가 생성되고 이후에는 생성되지 않는다.
    • 즉, 위에서의 함수 동등성에 의해 onChange 함수로 인해 input의 state가 변경되고 컴포넌트가 리렌더링 될 때 마다 onChangeId가 반응하고 원래는 재생성해야 하지만 (함수는 객체이므로 값이 변하면 내부가 변한다.) useCallback을 통해 재생성하지않고 따라서 리렌더링 하지도 않으며 따라서 불필요한 성능 낭비를 줄일 수 있게 된다.

레퍼런스 주소

https://ko.javascript.info/destructuring-assignment

https://www.daleseo.com/react-hooks-use-callback/

https://dolphinsarah.tistory.com/31

https://coding-w00se.tistory.com/41