useMemo, useCallback, React.memo

김석·2023년 9월 19일
0

React

목록 보기
6/14

1. useState 문제점

  • 여러 상태 변수 중 하나의 상태 변수가 useState로 변경된 경우, 그 함수형 컴포넌트 전체 본문이 다시 실행됨
  • 실제 DOM 업데이트는 변경된 부분에서만 발생하긴 해서, 성능상의 큰 문제가 발생하지는 않음
  • 하지만 무조건적으로 모든 것을 리렌더링하는 것은 효율적이지 않음

리렌더링을 useState만 유발하나?

  • 아님. 부모 컴포넌트가 리렌더링될 때 자식 컴포넌트도 리렌더링될 수 있음.
  • useContext로 구독 중인 컨텍스트 값이 변경되면, 해당 값을 사용하는 컴포넌트도 리렌더링됨.
  • props의 변화에 따라 컴포넌트 리렌더링됨.
  • useReducer 사용 시 상태 변경에 따라 리렌더링됨.
  • forceUpdate 같은 로직으로 강제 리렌더링 발생 가능.

2. useMemo

  • 이전에 계산한 값이 바뀌지 않음이 확실하면, 이전에 계산한 값을 재사용.
function countActiveUsers(users) {
  console.log('활성 사용자 수를 세는중...');
  return users.filter(user => user.active).length;
}

const count = useMemo(() => countActiveUsers(users), [users]);
  • 첫 번재 파라미터: 변수를 정의하는 함수
  • 두 번째 파라미터: 의존성 배열. 이 배열 안의 내용이 바뀌면, 우리가 등록한 함수를 호출해서 값을 연산하고, 만약 내용이 바뀌지 않았다면, 이전에 연산한 값을 재사용

3. useCallback

  • 함수들은 컴포넌트 리렌더링 시 계속 새로 만들어짐.
  • 사실 이 자체로는 큰 부하가 생기지 않지만, 한 번 만든 함수를 필요할 때만 새로 만들고, 재사용하는 것은 중요함.
  • 왜냐하면, 나중에 컴포넌트에서 props가 바뀌지 않았다면 virtual DOM에 새로 렌더링하는 것조차 하지 않고 컴포넌트 결과물을 재사용하는 최적화 작업을 할 때, 함수를 재사용하는 것이 필수적이기 때문.

사용법

  • useCallback으로 감싸줌
  • 의존성 배열 추가
const onChange = e => {
    const { name, value } = e.target;
    setInputs({
      ...inputs,
      [name]: value
    });
  };
const onChange = useCallback(
    e => {
      const { name, value } = e.target;
      setInputs({
        ...inputs,
        [name]: value
      });
    },
    [inputs]
  );
  • 함수 안에서 사용하는 상태나 props가 있다면, 의존성 배열에 꼭 넣어주어야 함.
  • 그렇지 않다면, 함수 내에서 참조하는 값은 가장 최신의 값이라는 보장을 할 수 없게 됨.

4. 중간 정리

<CreateUser
username={username}
email={email}
onChange={onChange}
onCreate={onCreate}
/>
<UserList users={users} onRemove={onRemove} onToggle={onToggle} />
  • 이 두 컴포넌트는 props 값에 의존하므로, 해당 값들 중 하나라도 바뀌면 리렌더링됨
  • onChange, onCreate, onRemove, onToggle 같은 콜백함수가, 만약 상위 컴포넌트인 App 본문 전체가 다시 실행되어 다시 정의된다면, 함수 참조 값이 바뀔 것이고, 이 때문에 CreateUser과 UserList는 리렌더링 될 것임
  • 따라서 useCallback을 사용하면 함수가 의존성 배열이 바뀌지 않는 이상 리렌더링 되지 않게 됨

5. React.memo

  • 컴포넌트의 props가 바뀌지 않았다면, 리렌더링을 방지하여 컴포넌트 리렌더링 성능을 최적화시켜줌

사용법

  • 감싸주기
export default React.memo(CreateUser);

6. 최종 정리

  • useState로 상태가 바뀌면 컴포넌트 본문이 모두 실행됨
  • 물론 virtual DOM에서 바뀐 부분만 렌더링하기 때문에 성능이 썩 나쁘진 않음
  • 우리가 최적화하는 부분은, 컴포넌트 본문이 모두 실행될 때, 바뀌는 부분 빼고는 계산하지 않게 하기
    1. 계산되지 않아도 되는 변수는 이전에 저장한 값으로 대신하기(useMemo)
    2. 함수 정의를 또 하지 않아도 되는 경우 이전에 정의한 함수 재사용(UseCallback, 대신 이 함수 내부에서 쓰는 상태 변수가 밖에서 바뀔 가능성이 있다면, 의존성 배열에 추가해야 함)
    3. 컴포넌트의 props가 바뀌지 않았다면, 리렌더링 방지하기(React.memo)
  • useMemo를 사용하면, 컴포넌트 본문 다시 실행될 때, 바뀌지 않음이 확실한 변수는 계산 안하고 이전에 계산한 값을 사용함.
  • useCallback을 사용하면, 컴포넌트 본문이 다시 실행될 때, 함수를 다시 생성하지 않고 기존의 함수를 사용함. 대신에 이 함수는 이전에 실행된 컴포넌트 본문의 상태 값에 묶여 있기 때문에, useCallback에서 사용하는 함수가 사용하는 상태 값이 변경 가능성이 있는 경우, 의존성 배열에 추가해야 함. 이럴 경우에는 함수가 어쩔 수 없이 다시 정의되게 됨.
  • React.memo를 사용하면, 본인 컴포넌트의 props가 바뀌지 않은 경우라면 부모가 리렌더링 되더라도 본인은 리렌더링 되지 않음. 본인 props에 함수가 있는 경우, 그 함수는 부모에서 useCallback 함수여야 함. 그래야 부모가 리렌더링 되더라도 그 함수는 다시 생성되지 않으므로 React.memo를 사용하는 의미가 있음.

출처

https://react.vlpt.us/basic/17-useMemo.html
https://react.vlpt.us/basic/18-useCallback.html
https://react.vlpt.us/basic/19-React.memo.html

profile
handsome

0개의 댓글