👣 10장을 학습할 때 만든 Todo-app은 데이터가 적어 랙(lag)이 발생하지 않는다.
👣 랙을 경험하기 위해 createBulkTodos 함수를 만들어 많은 데이터를 렌더링하도록 한다.
//App.js
function createBulkTodos() {
const array = [];
for (let i = 1; i <= 2500; i++) {
array.push({
id: i,
text; `할 일 ${i}1,
checked: false,
});
}
return array;
}
const App() => {
const [todos, setTodos] = useState(createBulkTodos);
(...)
👀 useState(createBulkTodos) : 파라미터를 함수 형태로 넣어 주어 컴포넌트가 처음 렌더링될 때만 createBulkTodos 함수 실행
👣 React DevTools(리액트 전용 개발자 도구)를 사용해 성능 분석
👣 리렌더링이 발생하는 경우
🤖 자신이 전달받은 props가 변경될 때
🤖 자신의 state가 바뀔 때
🤖 부모 컴포넌트가 리렌더링될 때
🤖 forceUpdate 함수가 실행될 때
👣 컴포넌트의 리렌더링 방지 - shouldComponentUpdate 라이프사이클 사용
👣 함수 컴포넌트에서는 라이프 사이클 사이클을 사용할 수 없다.
👀 따라서 React.memo 함수 사용
//TodoListItem.js
(...)
export default React.memo(TodoListItem);
👀 useState의 함수형 업데이트
🤖 함수형 업데이트 : setTodos에 상태 업데이트를 어떻게 할지 정의해주는 함수를 파라미터로 넣음
(...)
const onInset = useCallback(text => {
const todo = {
id: next.current,
text,
checked: false,
};
setTodos(todos => todos.concat(todo));
}, []);
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,
),
);
}, []);
(...)
👀 useReducer 사용하기
//App.js
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, dispatch] = useReducer(todoReducer, undefined, createulkTodos);
(...)
🤖 useReducer의 두 번째 파라미터에 undefined 넣고, 세 번째 파라미터에 createBulkTodos를 넣으면 컴포넌트가 맨 처음 렌더링될 때만 createBulkTodos 함수가 호출된다.
👣 리액트 컴포넌트에서 상태 업데이트를 할 때 불변성을 지키는 것은 매우 중요하다.
👣 기존의 값을 직접 수정하지 않으면서 새로운 값을 만들어 내는 것을 '불변성을 지킨다'라고 한다.
👣 불변성이 지켜지지 않으면 객체 내부의 값이 새로워져도 바뀐 것을 감지하지 못한다.
👣 전개 연산자(... 문법)을 사용하여 객체나 배열을 복사하면 내부의 값이 완전히 새로 복사되는 것이 아니라 가장 바깥쪽에 있는 값만 복사된다.
👀 배열이나 객체 구조가 복잡한 경우 immer라는 라이브러리의 도움을 받을 수 있다.
👣 리스트에 관련된 컴포넌트를 최적화할 때는 리스트 내부에서 사용하는 컴포넌트도 최적화해야하고, 리스트로 사용되는 컴포넌트 자체도 최적화해 주는 것이 좋다.
👀 최적화 준비
$ yarn add react-virtualized
👣 최적화 수행 전 각 항목의 실제 크기를 px단위로 알아내야 한다.
👀 TodoList 수정
//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={512}
height={513}
rowCount={todos.length}
rowHeight={57}
rowRenderer={rowRenderer}
list={todos}
style={{ outline: 'none' }}
/>
);
};
export default React.memo(TodoList);
👣 rowRenderer 함수는 react-virtualized의 List 컴포넌트에서 각 TodoItem을 렌더링할 때 사용하고, 이 함수를 List 컴포넌트의 props로 설정해주어야 한다.
👣 rowRenderer 함수는 파라미터에 index, key, style 값을 객체 타입으로 받아와서 사용한다.
👀 TodoListItem 수정
//TodoListItem.js
import React from 'react';
import {
MdCheckBoxOutlineBlank,
MdCheckBox,
MdRemoveCircleOutline,
} 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);
👣 TodoListItem-virtualized 클래스를 만든 것은 컴포넌트 사이에 테두리를 제대로 쳐 주고, 홀/짝수 번째 항목에 다른 배경 색상을 설정하기 위해서이다.
//TodoListItem.scss
.TodoListItem-virtualized {
& + & {
border-top: 1px solid #dee2e6;
}
&:nth-child(even) {
background: #f8f9fa;
}
}
(...)