[TodoApp개발일지 #2] 앱 최적화하기

김유진·2022년 11월 2일
0

React

목록 보기
44/64

먼저 내가 만들어두었던 App에 어떠한 문제가 있는지 알아보기 위해서 2500개의 Todo를 임의로 집어넣었다.

엄청 느리다..진짜 엄청엄청..ㅠㅠ

이전에 미리 갈아놓은 크롬 개발자 도구로 어떤 성능을 가지고 돌아가고 있는지 확인해보자.

헐..랜더하는데 600ms 대가 나온다. 이거는 최적화 안하면 안된닷.

이렇게 보면 누가 가장 오래 걸렸는지도 볼 수 있는데, 여기서 스크롤을 내리다보면 체크박스만 toggle하는 것과 관계없는 컴포넌트도 리랜더링 된 것을 알 수 있다.

얘네는 도대체 왜 리랜더링 된거냐구..

컴포넌트가 리랜더링 되는 이유가 무엇일까?

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

할 일 1의 체크박스를 토글하는 순간, App 컴포넌트의 state가 변경이 되면서 App 컴포넌트 전체가 리랜더링된다. 가장 최상위에 있는 부모 컴포넌트가 리랜더링 되었으니까, 당연히 아래에 있는 수많은 컴포넌트도 리랜더링이 되는 것이다.

React.memo를 사용하여 컴포넌트의 성능을 최적화한다.

함수 컴포넌트에서는 라이프사이클 메서드를 따로 사용할 수 없다. 그렇기 때문에 React.memo라는 함수를 이용한다.
컴포넌트의 props가 바뀌지 않았더라면, 리랜더링하지 않도록!! 함수 컴포넌트의 리랜더링 성능을 초기화해줄 수 있다.

사실 사용법이 너무너무 간단해서 뻘쭘하다.
최적화해주고 싶은 컴포넌트의 마지막줄에 아래 코드만 추가한다.

export default React.memo(TodoInsert);

사실 이걸로 최적화가 끝난 게 아니다.
얘네가 받은 props가 워낙 많아서 props 하나만 막는다고 최적화가 끝나는 것이 아니므로 끝날때까지 긴장햇!!

React.memo()를 쓰기에 좋은 조건

같은 props로 렌더링이 자주 일어나는 컴포넌트 -> React.memo는 함수형 component에 적용되어 같은 props에 같은 렌더링 결과를 제공하기 때문에 같은 props로 자주 렌더링 될거라 예상될 때 사용해야 한다.

todos 배열의 변화를 제어해보자.

나의 프로젝트는 App.js가 TodoList의 모든 변화를 todos 배열로 받고 있다.
onRemoveonToggle함수는 todos 배열이 업데이트되면 새롭게 바뀐다.
이 두 함수는 배열 상태를 새로 참조해야 한다. 그래서 최신 상태의 todos를 참조하는데,, 그래서 todos가 바뀔 대마다 함수가 새로 메모리도 배정되고 새롭게 만들어지는 것이다.
이것을 어떻게 제어해볼 수 있을까?

현재 하위 컴포넌트에 랜더링을 최대한 막을 수 있는 상관없는 친구들에 React.memo를 달아두었다. 나의 App.js 구조가 아래와 같이 되어 있어


  return (
   <>
    <TodoTemplate>
      <MyDatePicker dateFormat = "yyyy/MM/dd" selected = {date} onChange = {date => setDate(date)}
      locale = {ko} placeholderText='Weeks start on Monday'/>
      <TodoInsert onInsert = {onInsert}/>
      <TodoList todos = {todos} onRemove={onRemove} onToggle ={onToggle}/>
    </TodoTemplate>
   </>
  );
}

가장 최상위의 TodoTempalte이 어쩔 수 없이 chiledren을 props로 받기 때문에 최상위 컴포넌트 랜더링을 피할 수 없다.
그래서 최적화 시켜도 별다른 변화가 없다.

실망하지 말기!

1. useState의 함수형 업데이트

const onInsert = useCallback( 
    (text, checked) => {
      const newtodo ={
        id : nextId.current,
        text : text,
        checked : checked,
      };
      nextId.current++;
      setTodos([...todos, newtodo]);
      localStorage.setItem("TODO", JSON.stringify([...todos, newtodo]));
    },[todos]
  );

기존의 프로젝트는 이렇게 setTodo함수에 새로운 상태를 파라미터로 아예 넣어버렸다. 이렇게 하지 말고, 상태를 어떻게 업데이트 할 것인지 정의해주는 업데이트 함수를 넣을 수 있다.

const [number, setNumber] = useState(0);
const onIncrease = useCallback(
  () => setNumber(prevNumber => prevNumber + 1), [],)};

이 개념을 바탕으로 우리 프로젝트도 수정을 해보자.

  const onInsert = useCallback( 
    (text, checked) => {
      const newtodo ={
        id : nextId.current,
        text : text,
        checked : checked,
      };
      nextId.current++;
      setTodos(todos => todos.concat(newtodo));
      localStorage.setItem("TODO", JSON.stringify([...todos, newtodo]));
    },[todos]
  );

위 코드는 먼저 onInsert를 수정한 내용이다.
onRemoveontoggle도 비슷한 내용을 수정을 진행해보자!

2. useReducer 사용하기

0개의 댓글