프론트엔드 첫걸음

Todo App 성능개선 (React.memo + 함수형 setState) 본문

개발 공부/React

Todo App 성능개선 (React.memo + 함수형 setState)

차정 2022. 8. 28. 00:04
리액트를 다루는 기술 10장, 11장

 

1. 임의로 많은 데이터 입력하기

function createBulkTodos() {
  const array = [];
  for (let i = 0; i < 2500; i++) {
    array.push({
      id: i,
      text: `${i} 번째 할일`,
      checked: false,
    });
  }
  return array;
}

const App = () => {
  const [todos, setTodos] = useState(createBulkTodos);

  const nextId = useRef(2501);

useState 안에 넣는 초기값에 함수 createBulkTodos() 라고 넣지 않고, createBulkTodos 함수를 넣으면 컴포넌트가 처음 렌더링 될 때만 createBulkTodos 함수가 실행된다.

2. 성능측정하기

1. 크롬 확장프로그램 React Developer Tools 설치

https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=ko

2. Profiler 녹화버튼을 누르고 화면에 변화가 반영되면 녹화 한번 더 눌러 결과 확인

Render duration- 리렌더링에 소요된 시간 확인

3. 컴포넌트

3. 느려지는 원인

리렌더링이 발생하는 상황은 다음과 같다.

  • 자신이 전달받은 props가 변경될 때
  • 자신의 state가 바뀔 때
  • 부모컴포넌트가 리렌더링 될 때
  • forceUpdate함수가 실행될 때

현재 1개만 클릭해도 나머지 2499개가 모두 리렌더링 되는상황. TodoListItem컴포넌트가 받은 props (todo) 가 바뀌지 않았을 때도 리렌더링 되는 것이 문제이다.

4. React.memo를 사용하여 컴포넌트 성능 최적화

  • react.memo는 컴포넌트에 들어가는 모든 props를 확인한 뒤, 이를 기존의 props 값과 비교하도록 리액트에 전달한다.
    그리고 props의 값이 바뀐 경우에만 컴포넌트를 재실행 및 재평가 한다.
    부모컴포넌트가 변경되었음에도 자식컴포넌트에 내려주는 props는 변하지 않았다면 컴포넌트의 실행을 건너뛴다.
    재실행되지 않는 자식컴포넌트에 포함된 다른 컴포넌트들도 재실행되지 않는다.
const TodoListItem = ({ todo, onRemove, onToggle }) => {
  const { id, text, checked } = todo;
  return (
    <div className="TodoListItem">
      <div
        className={checked ? 'checkbox checked' : 'checkbox'}
        onClick={() => onToggle(id)}
      >
        {checked ? <MdOutlineCheckBox /> : <MdCheckBoxOutlineBlank />}

        <div className="text">{text} </div>
      </div>
      <div className="remove" onClick={() => onRemove(id)}>
        <IoIosRemoveCircleOutline />
      </div>
    </div>
  );
};

export default React.memo(TodoListItem);

 

5. useState의 함수형 업데이트

setState함수의 인자로 상태 대신 이전 상태를 전달하여 상태를 업데이트하는 함수 를 넣어준다.

  • 리팩토링 전
    setState(todos 배열)
    const onRemoveHandler = useCallback(
      (id) => {
        setTodos(todos.filter((todo) => todo.id !== id));
      },
      [todos],
    );
    todos 배열 상태를 기준으로 update= todos가 바뀔때마다 useCallback 함수 재생성 해서 setState 안의 상태 업데이트해야함
    => 의존성 배열에 todos 넣음
  • 리팩토링 후
    setState(todos 이전상태를 전달하여 상태를 업데이트 하는 함수)
      const onRemoveHandler = useCallback(
      (id) => {
        setTodos(todos => todos.filter((todo) => todo.id !== id));
      },
      [],
    );
    바로 이전상태를 전달하여 상태를 업데이트 한다 = todos 바껴도 이전 상태를 기준으로 업데이트 하는 함수는 바뀌지 않음
    => 의존성 배열에 todos 넣을 필요 없음

 

6. 리팩토링 후 성능 확인

duration이 약 1/20로 줄었다.

 

 

클릭한 요소만 리렌더링 된다.

'개발 공부 > React' 카테고리의 다른 글

immer 사용법  (0) 2022.08.29
불변성을 지키면서 setState  (0) 2022.08.28
Redux  (0) 2022.08.23
Styled Component 심화 1 - Component Selector  (0) 2022.08.18
Form 제출 시 input값 가져오는 두가지 방법  (0) 2022.08.14