React/일정관리 프로젝트
[React TodoList] 많은 데이터 렌더링, React.memo, 성능 최적화
Judith Hopps
2023. 3. 14. 18:34
반응형
React TodoList 보완하기
많은 데이터 렌더링 생성하기
import React , {useCallback, useState,useRef} from "react";
import TodoTemplate from "./TodoTemplate";
import TodoInsert from "./TodoInsert";
import TodoList from "./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)
const nextId = useRef(2500);
const onInsert = useCallback(
text => {
const todo = {
id : nextId.current,
text,
checked :false,
};
setTodos(todos.concat(todo))
nextId.current +=1;
},[todos],
)
const onRemove = useCallback(
id => {
setTodos(todos.filter(todo=>todo.id !== id))
},[todos]
)
const onToggle = useCallback(
id => {
setTodos(
todos.map(todo =>
todo.id === id ? {...todo, checked:!todo.checked}:todo,)
)
},[todos]
)
return (
<TodoTemplate>
<TodoInsert onInsert={onInsert}/>
<TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
</TodoTemplate>
)
}
export default App;
성능 모니터링 (개발자 도구 - performance - 녹화)

1.4초로 성능이 매우 나쁘다는 것을 알 수 있다.
성능이 느린 이유
'할 일 : 1 ' 항목 체크시 app.state가 변경 => app.js 리렌더링 => todoList 등 자식 컴포넌트 렌더링
리렌더링 발생 원인
1. 자신이 전달받은 props가 변경될 때
2. 자신의 state가 변경될 때
3. 부모 컴포넌트가 리렌더링 될 때
4. forceUpdate 함수가 실행될 때
React.memo를 사용해 컴포넌트 성능 최적화
리렌더링을 방지하기 위해 React.memo를 사용하여 props가 바뀌지 않으면 렌더링하지 않도록 설정한다.
todoListItem.js
import React from 'react';
(...)
export default React.memo(TodoListItem);

onToggle, onRemove 함수가 바뀌지 않게 하기
현재 프로젝트는 todos 배열이 업데이트 되면 onRemove와 onToggle 함수도 새로 바뀐다.
컴포넌트 최적화를 위해 useState 또는 useReducer를 사용한다.
1. useState의 함수형 업데이트
setTodos 함수를 사용할 때 새로운 상태를 파라미터로 넣어 준다.
app.js
import React , {useCallback, useState,useRef} from "react";
import TodoTemplate from "./TodoTemplate";
import TodoInsert from "./TodoInsert";
import TodoList from "./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)
const nextId = useRef(2500);
const onInsert = useCallback(
text => {
const todo = {
id : nextId.current,
text,
checked :false,
};
setTodos(todos=> todos.concat(todo))
nextId.current +=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;

500ms로 줄은 걸로 보아 성능이 향상된 것을 알 수 있다.
2. useReducer 사용하기
두 번째 파라미터에 초기 상태를 넣어주어야 하지만 undefined를 설정하고 세 번째 파라미터에 초기 상태 생성하는 함수를 넣어 컴포넌트가 맨 처음 렌더링 될 때 1회 함수를 호출한다.
import React , {useCallback, useState,useRef, useReducer} from "react";
import TodoTemplate from "./TodoTemplate";
import TodoInsert from "./TodoInsert";
import TodoList from "./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 'Delete' :
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)
const nextId = useRef(2500);
const onInsert = useCallback(
text => {
const todo = {
id : nextId.current,
text,
checked :false,
};
dispatch({type:'Insert',todo})
// setTodos(todos=> todos.concat(todo))
nextId.current +=1;
},[],
)
const onRemove = useCallback(
id => {
// setTodos(todos=> todos.filter(todo=>todo.id !== id))
dispatch({type:'Delete',id})
},[]
)
const onToggle = useCallback(
id => {
dispatch({type:'Toggle',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;
useState와 useReducer 둘 중 어느 것을 사용해도 되고 성능은 비슷하다.
useReducer를 사용하면 상태를 업데이트 하는 로직을 컴포넌트 바깥에 둘 수 있다는 장점이 있다.
반응형