[React] 컴포넌트 성능 최적화_②onToggle, onRemove 함수가 바뀌지 않게 하기

겨레·2024년 11월 27일

[React] 리액트 스터디

목록 보기
69/95

✔ 사실 React.memo를 사용하는 것만으로 컴포넌트 최적화가 끝나지는 않는다. todos 배열이 업데이트되면 onRemove와 onToggle 함수도 새롭게 바뀌기 때문...

✔ onRemove와 onToggle 함수는 배열 상태를 업데이트하는 과정에서 최신 상태의 todos를 참조하기 때문에 todos 배열이 바뀔 때마다 함수가 새로 만들어진다.

이렇게 함수가 계속 만들어지는 상황을 방지하는 두 가지 방법이 있다.

👉 ① useState의 함수형 업데이트 기능을 사용하는 것
👉 ② useReducer를 사용하는 것


① useState의 함수형 업데이트

기존에 setTodos 함수를 사용할 때 새로운 상태를 파라미터로 넣어 주었다.

setTodos를 사용할 때 새로운 상태를 파라미터로 넣는 대신, 상태 업데이트를 어떻게 할지 정의해 주는 업데이트 함수를 넣을 수도 있는데 이를 함수형 업데이트라고 한다.

  • 예시
const [number, setNumber] = useState(0);
// prevNumbers는 현재 number 값을 가리킵니다.
const onIncrease = useCallback(
() => setNumber(prevNumber => prevNumber + 1),
[],
);

✔ setNumber(number+1)을 하는게 아니라,
✔ 위 코드처럼 어떻게 업데이트할지 정의해 주는 업데이트 함수를 넣어준다.
✔ 그러면 useCallback을 사용할 때, 두 번째 파라미터로 넣는 배열에 number를 넣지 않아도 된다.


onToggle, onRemove 함수에서 useState의 함수형 업데이트를 사용해보자! (onInsert 함수도 함께 수정함)
  • App.jsx
import React, { useRef, useState, useCallback } from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
 
function createBulkTodos() {
  const array = [];
  for (let i = 1; i <= 2500; i++) {
    array.push({
      id: i,
      text: `할 일 ${i}`,
      checked: false,
    });
  }
  return array;
}
 
const App = () => {
  const [todos, setTodos] = useState(createBulkTodos);
 
  // 고윳값으로 사용될 id
  // ref를 사용하여 변수 담기
  const nextId = useRef(4);
 
  const onInsert = useCallback(text => {
    const todo = {
      id: nextId.current,
      text,
      checked: false,
    };
    setTodos(todos => todos.concat(todo));
    nextId.current += 1; // nextId 1씩 더하기
  }, []);
 
  const onRemove = useCallback(id => {
    setTodos(todos => todos.filter(todo => todo.id != = id));
  }, []);
 
  const onToggle = useCallback(id => {
    setTodos(todos =>
      todos.map(todo =>
        todo.id === id ? { ...todo, checked: !todo.checked } : todo,
      ),
    );
  }, []);
 
  return (
    <TodoTemplate>
      <TodoInsert onInsert={onInsert} />
      <TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
    </TodoTemplate>
  );
};
 
export default App;



② useReducer 사용

useState의 함수형 업데이트를 사용하는 대신, useReducer를 사용해도 onToggle과 onRemove가 계속 새로워지는 문제를 해결할 수 있다.

  • App.jsx
import React, { useReducer, useRef, useCallback } from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
 
function createBulkTodos() {
  const array = [];
  for (let i = 1; i <= 2500; i++) {
    array.push({
      id: i,
      text: `할 일 ${i}`,
      checked: false,
    });
  }
  return array;
}
 
function todoReducer(todos, action) {
  switch (action.type) {
    case 'INSERT': // 새로 추가
      // { type: 'INSERT', todo: { id: 1, text: 'todo', checked: false } }
      return todos.concat(action.todo);
    case 'REMOVE': // 제거
      // { type: 'REMOVE', id: 1 }
      return todos.filter(todo => todo.id != = action.id);
    case 'TOGGLE': // 토글
      // { type: 'REMOVE', id: 1 }
      return todos.map(todo =>
        todo.id === action.id ? { ...todo, checked: !todo.checked } : todo,
      );
    default:
      return todos;
  }
}
 
const App = () => {
  const [todos, dispatch] = useReducer(todoReducer, undefined, createBulkTodos);
 
  // 고윳값으로 사용될 id
  // ref를 사용하여 변수 담기
  const nextId = useRef(2501);
 
  const onInsert = useCallback(text => {
    const todo = {
      id: nextId.current,
      text,
      checked: false,
    };
    dispatch({ type: 'INSERT', todo });
    nextId.current += 1; // nextId 1씩 더하기
  }, []);
 
  const onRemove = useCallback(id => {
    dispatch({ type: 'REMOVE', id });
  }, []);
 
  const onToggle = useCallback(id => {
    dispatch({ type: 'TOGGLE', id });
  }, []);
 
  return (
    <TodoTemplate>
      <TodoInsert onInsert={onInsert} />
      <TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
    </TodoTemplate>
  );
};
 
export default App;

✔ useReducer를 사용할 때는 원래 두 번째 파라미터에 초기 상태를 넣어 줘야함.

지금은 그 대신 두 번째 파라미터에 undefined를 넣고, 세 번째 파라미터에 초기 상태를 만들어 주는 함수인 createBulkTodos를 넣었음. 이렇게 하면 컴포넌트가 맨 처음 렌더링될 때만 createBulkTodos 함수가 호출됨.

✔ useReducer를 사용하는 방법은 기존 코드를 많이 고쳐야 한다는 단점이 있지만, 상태를 업데이트하는 로직을 모아서 컴포넌트 바깥에 둘 수 있다는 장점이 있음.

✔ 성능상으로는 두 가지 방법이 비슷하기 때문에 어떤 방법을 선택할지는 여러분의 취향에 따라 결정하면 됨.

profile
호떡 신문지에서 개발자로 환생

0개의 댓글