지난 시간 사용했던 React.memo만으로는 컴포넌트 최적화는 끝나지 않는다.
현재 프로젝트에서는 todos 배열이 바뀔 때마다 onRemove와 onToggle함수가 새롭게 바뀌기 때문이다. onRemove와 onToggle함수는 배열 상태를 업데이트하는 과정에서 최신 상태의 todos를 참조하기 때문에 todos배열이 바뀔 때마다 함수가 새로 만들어진다.
이렇게 함수가 계속 만들어지는 상황을 방지하는 방법은
1) useState의 함수형 업데이트 기능 사용하기
2) useReducer사용하기
기존 setTodos 함수 사용시 새로운 상태를 피라미터로 넣어 주었다.
setTodos를 사용할때 새로운 상태를 파라미터로 넣는 대신 상태 업데이트를 어떻게 할지 정의해주는 업데이트 함수를 넣을 수도 있다. 이를 함수형 업데이트라고 한다.
const [number, setNumber] = useState(0);
// prevNumbers는 현재 number 값을 가리킵니다.
const onIncrease = useCallback(
() => setNumber(prevNumber => prevNumber + 1),
[],
);
setNumber(number+1)이 아니라, 어떻게 업데이트할지 정의해주는 업데이트 함수를 넣는다. 그러면 useCallback을 사용할 때 두 번째 파라미터로 넣는 배열에 number를 넣지 않아도 된다.
// 기존
const onInsert = useCallback(
(text) => {
const todo = {
id: nextID.current,
text,
checked: false,
};
setTodos(todos.concat(todo));
nextID.current += 1;
},
[todos]
);
// 바꾼 코드
const onInsert = useCallback((text) => {
const todo = {
id: nextID.current,
text,
checked: false,
};
setTodos((todos) => todos.concat(todo));
nextID.current += 1;
}, []);
import React, { useState, useReducer, useRef, useCallback } from "react";
// import SassComponent from "./Component/SassComponent";
// import CSSModule from "./Component/CSSModule";
import TodoTemplate from "./TodoList/TodoTemplate";
import TodoInsert from "./TodoList/TodoInsert";
import TodoList from "./TodoList/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":
return todos.concat(action.todo);
case "REMOVE":
return todos.filter((todo) => todo.id !== action.id);
case "TOGGLE":
return todos.map((todo) =>
todo.id === action.id ? { ...todo, checked: !todo.checked } : todo
);
default:
return todos;
}
}
const App = () => {
//const [todos, setTodos] = useState(createBulkTodos);
const [todos, dispatch] = useReducer(todoReducer, undefined, createBulkTodos);
// ID값은 렌더링되는 정보가 아님, 값이 바뀐다고 해서 컴포넌트가 리렌더링 될 필요 없음
const nextID = useRef(2501);
// useCallback으로 감싸는 이유: 컴포넌트의 성능을 아끼기 위함.
// props로 전달해야 할 함수를 만들 때는 useCallback을 사용해 감싸도록 하자.
const onInsert = useCallback((text) => {
const todo = {
id: nextID.current,
text,
checked: false,
};
dispatch({ type: "INSERT", todo });
nextID.current += 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함수가 호출된다.
해당 방법은 상태 업데이트 로직을 모아서 컴포넌트 바깥에 둘 수 있다는 장점이 있다.