useCallback, useMemo 알고 사용하기

정규영·2023년 3월 11일
0

React

목록 보기
2/4

지금까지 리액트 관련 프로젝트를 진행할 때 useMemo, useCallback 개념은 알고는 있었지만 사용해 본 기억이 극히 드물었기 때문에 실제 어느 상황에 쓰면 좋을지 알기 위해서 복습겸 다시 공부해보는 시간을 가져볼 것이다.

Memoization

연산된 결과 값을 특정 배열(메모리)에 저장해 두고 이전 값과 비교했을 때 결과가 동일하면 재사용해주는 기법이다.

React 에서의 메모이제이션

리액트에서 제공하는 메모이제이션 종류는 다음과 같다. React.memo(컴포넌트), useMemo(() => 함수의 리턴 값, []), useCallback(() => {함수 자체}, [])

  • React.memo() 같은 경우는 props의 값으로 변경을 확인.
  • useMemo(), useCallback() 은 두 번째 인자의 [] 배열의 값(dependency)을 기준으로 변경사항들을 확인한다.

useCallback

메모이제이션 된 함수 를 반환한다.

  • 예시로 useCallback(() => 함수, [deps]) 값이 있으면 deps 에 의존하고 있는 값이 변하면 함수 에 등록된 함수를 반환한다.
  • 함수를 반환하기 때문에 그 함수를 가지는 const 변수에 초기화 시켜주기.
  • 주로 외부에서 값을 가져오는 무거운 api를 호출하는 경우에 사용된다.

useCallback re-render 막기

react 가 재렌더링 되는 조건은 크게 props, state 가 변경 되었을 때 변경이 된다. 즉, 불러오는데 오래 걸리거나 무거운 api를 불러오는 부분도 매 번 다시 불러온다는 뜻인데, useCallback hook을 사용해서 컴포넌트가 재렌더링이 되어도 api를 불러오는 함수의 재렌더링을 막아 볼 것이다.

// 부모 컴포넌트
const Home = () => {
  const [ex, setEx] = useState(0);
  const [watch, setWatch] = useState(0);
  
  // ex 변수가 변경될 때만 fetch가 동작하도록 하기.
  function exManyAPI() {
    fetch("https://jsonplaceholder.typicode.com/posts")
        .then(res => res.json())
        .then(data => {
          console.log(data);
          return data;
        })
  }
  
  // watch 값이 변경될 때만 재렌더링 시켜주기.
  const getAPI = useCallback(() => {exManyAPI()}, [watch]);
  console.log('parent re-render');
  
  return (
      <div>
        <button onClick={() => setEx((prev) => (prev + 1))}>X</button>
        <button onClick={() => setWatch((prev) => (prev + 1))}>Y</button>
        {/* Profile 이라는 자식 컴포넌트에 useCallback 함수가 적용된 apiGet 함수를 보내주기. */}
		{/* props로 보내지는 onSave 함수에 getApi가 아닌 exManyAPI를 넣어주면 
        부모 컴포넌트의 state가 변경될 때 마다 값을 새로 불러 줄 것이다.*/}
        <Profile onSave={getAPI} ex={ex}/>
      </div>
  );
}
// 자식 컴포넌트
function Profile({onSave, ex}) {
  // 부모 컴포넌트의 watch를 변경시켜주는 버튼을 누르면 재렌더링이 되고,
  // ex state를 변경 시켜주는 버튼을 눌렀을 때는 onSave() === apiGet() 함수가 재렌더링이 되지 않는다.
  useEffect(() => {
    onSave();
    // 부모의 값이 변경되면 자식한테도 변경된 값을 다시 보내줘야되기 때문에 [] 안에 onSave 넣어주기.
  }, [onSave]);

  return(
      <div>
            <p>profile {ex}</p>
      </div>
  )

}

결과

getApi함수는 useCallback으로 감싸져 있고 두 번째 인자로 watch 를 넣어줬기 때문에
watch state를 변경해주는 함수를 넣었을 때 api 함수를 다시 불러오는 것을 볼 수 있고, 반대로 ex state를 변경시켜주는 버튼을 눌렀을 때 재렌더링이 되지 않는 것을 볼 수 있다.

useMemo

useMemo로 이전에 연산한 값을 재사용하는 메모이제이션을 적용할 수 있다.

input 최적화 시키기

const Home = () => {
  const [item, setItem] = useState("");
  const [itemList, setItemList] = useState([]);
  const handleItem = (event) => {
    setItem(event.target.value);
  }
  const handleSubmit = (event) => {
    setItemList([...itemList, item]);
    event.preventDefault(); // form submit event로 인해 재로딩되지 않도록 해주기
    setItem("");
  };
  // 주목해야 될 부분!
  const itemCount = (item) => {
    console.log("item 개수를 셉니다");
    return item.length;
  };
  // count을 이렇게 두면 input창이 변경 될 때마다 console에 값이 계속 늘어난다.
  // const count = itemCount(itemList);
  // 여기까지 주목하기~!

  // count 을 변경시켜주기.
  const count = useMemo(() => itemCount(itemList), [itemList]);

  return (
      <>
        <form onSubmit={handleSubmit}>
          <input onChange={handleItem} value={item} />
          <button type="submit">submit</button>
        </form>
        <p>{itemList}</p>
        <p>itemList 개수 : {count}</p>
      </>
  );
}

export default Home;

결과

검색처럼 입력 때 마다 연관 검색어가 생기는 등의 기능을 구현할 때 말고 로그인, 회원가입 같이 입력이 다 되었을 때의 결과만 필요로 하는 부분 또는 컴포넌트 재렌더링이 사소한 동작에 인해 생기게 되는 부분이 있을 때 사용하면 좋을 것 같다는 생각이 든다.

profile
웹 프론트 개발일기

0개의 댓글