앞서 컴포넌트의 성능을 개선하기 위해 했던 과정들은 [Todo-List] 4. 컴포넌트 성능 개선 (1)를 참고하면 된다.
useState
의 함수형 업데이트를 사용하는 대신, useReducer
를 사용해도 onRemove
와 onToggle
가 계속 새로워지는 문제를 해결할 수 있다.App.js
를 다음과 같이 수정해보자.//App.js
import React, { useReducer, useRef, useCallback } from 'react';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
import TodoTemplate from './components/TodoTemplate';
function createBulkTodos() {
const array = [];
for(let i = 1; i <= 2000; 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: 'TOGGLE', 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(2001);
const onInsert = useCallback(text => {
const todo = {
id: nextId.current,
text,
checked: false,
};
dispatch({type: 'INSERT', todo});
nextId.current += 1; // id 값 1씩 증가
}, []);
const onRemove = useCallback(id => {
dispatch({type: 'ONREMOVE', 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
함수를 호출한다.useReducer
은 기존 코드를 많이 고쳐야 하지만, 상태
를 업데이트 하는 로직을 컴포넌트 바깥에 둘 수 있어서 가독성
이 좋아지는 것 같다.
👉 둘 다 같은 기능이기 때문에 상황에 맞춰서 사용할 수 있도록 하자.(나는 useState
가 더 편한듯..)✍
컴포넌트
를 최적화할 때는 리스트 내부에서 사용하는 컴포넌트도 최적화 해야하고, 리스트로 사용되는 컴포넌트 자체도 최적화 하는 것이 좋다.TodoList.js
를 다음과 같이 수정하였다.// TodoList.js
import React from "react";
import TodoListItem from "./TodoListItem";
import './TodoList.scss';
const TodoList = ({ todos, onRemove, onToggle }) => {
(...)
};
export default React.memo(TodoList);
리스트 컴포넌트
에서 스크롤되기 전에 보이지 않는 컴포넌트는 렌더링하지 않고 크기만 차지하게끔 할 수 있다.// yarn을 사용하여 설치해보자.
$ yarn add react-virtualized
react-virtualized
에서 제공하는 리스트 컴포넌트를 사용하여 TodoList
컴포넌트 성능을 최적화 해보자.크기를 확인할 때 두 번째 항목
을 확인해야한다.
👉 두 번째 항목부터 테두리가 포함되어 있기 때문임.
TodoList.js
를 다음과 같이 수정했다.//TodoList.js
import React, { useCallback } from "react";
import { List } from 'react-virtualized';
import TodoListItem from "./TodoListItem";
import './TodoList.scss';
const TodoList = ({ todos, onRemove, onToggle }) => {
const rowRenderer = useCallback(
({ index, key, style }) => {
const todo = todos[index];
return (
<TodoListItem
todo = {todo}
key = {key}
onRemove = {onRemove}
onToggle = {onToggle}
style = {style}
/>
);
}, [onRemove, onToggle, todos]
);
return (
<List
className="TodoList"
width={495.2} // 전체 크기
height={57} // 전체 높이
rowCount={todos.length} // 항목 개수
rowHeight={57} // 항목 높이
rowRenderer={rowRenderer} // 항목을 렌더링할 때 사용하는 함수
list={todos} // 배열
style={{ outline: 'none' }} // List에 기본 적용되는 outline 스타일 제거
/>
);
};
export default React.memo(TodoList);
리스트 컴포넌트
를 사용하기 위해 rowRenderer
라는 함수를 사용했는데, 이 함수는 react-virtualized의 리스트 컴포넌트
에서 각 TodoItem
을 렌더링할 때 사용한다.리스트 컴포넌트
의 props
로 설정해 주어야 하는데 주의할 점으로 파라미터
에 index
, key
, style
값을 객체 타입으로 받아와야 한다.전체 크기
, 각 항목의 높이
, 각 항목을 렌더링할 때 사용해야 하는 함수
, 배열을 props
로 넣어주면 전달받은 props
를 사용하여 자동으로 최적화해준다.TodoListItem.js
를 다음과 같이 수정했다.//TodoListItem.js
import React from "react";
import {
MdCheckBox,
MdRemoveCircleOutline,
MdCheckBoxOutlineBlank,
} from 'react-icons/md';
import cn from 'classnames';
import './TodoListItem.scss';
const TodoListItem = ({ todo, onRemove, onToggle, style }) => {
const { id, text, checked } = todo;
return (
<div className="TodoListItem-virtualized" style={style}>
<div className="TodoListItem">
<div className={cn('checkbox', {checked})} onClick={() => onToggle(id)}>
{checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
<div className="text">{text}</div>
</div>
<div className="remove" onClick={() => onRemove(id)}>
<MdRemoveCircleOutline />
</div>
</div>
</div>
);
};
export default React.memo(TodoListItem);
render
함수에서 기존에 보여 주던 내용을 TodoListItem-virtualized
라는 className
으로 설정하여 div
로 한번 감싸주었고, props
로 받아온 style을 적용시켰다.TodoListItem.scss
을 다음과 같이 수정했다.//TodoListItem.scss
.TodoListItem-virtualized {
& + & {
border-top: 1px solid #dee2e6;
}
&:nth-child(even) {
background: #f8f9fa;
}
}
(...)
홀수/짝수
항목에 다른 배경 색상이 적용된 것을 확인할 수 있다.React.memo
를 일일이 사용해야 한다기 보다는 리스트와 관련된 컴포넌트를 만들 때 보여줄 항목이 많거나, 업데이트가 자주 발생하면 오늘 해보았던 방법을 사용하여 최적화 하도록 하자.