랙(lag) 경험을 위해 많은 데이터 렌더링
//App.js
import React, {useState, 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:1,
text:`할 일 ${i}`,
checked: false,
});
}
return array;
}
const App = () => {
const [todos, setTodos] = useState(createBulkTodos);
const nextId = useRef(2501);
(...)
};
export default App;
크롬 개발자 도구 - Performance 탭 녹화 ⇒ 성능 분석 결과가 나타남
리렌더링이 발생하는 상황
※ 리렌더링이 불필요할 때는 리렌더링을 방지해줘야 함 ⇒ 컴포넌트 리렌더링 성능 최적화
방법1) shouldComponentUpdate 라이프 사이클 사용
방법2) 함수형 컴포넌트 - React.memo 함수 사용
→ 컴포넌트의 props가 바뀌지 않았다면, 리렌더링하지 않도록 설정 가능
→ 사용법 : 컴포넌트를 만들고 감싸 주기
//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 }) => {
(...)
};
export default **React.memo(TodoListItem);**
onRemove와 onToggle 함수는 배열 상태 업데이트 과정 중 최신 상태의 todos를 참조하기 때문에 todos 배열이 바뀔 때마다 함수가 새로 만들어짐
함수가 계속 만들어지는 상황 방지
방법1) useState의 함수형 업데이트 기능 사용
방법2) useReducer 사용
함수형 업데이트 : setTodos를 사용할 때 새로운 상태를 파라미터로 넣는 대신, 상태 업데이트를 어떻게 할지 정의해 주는 업데이트 함수
const [number, setNumber ] = useState(0);
const onIncrease = useCallback(
() => **setNumber(prevNumber => prevNumber + 1)**,
[],
}
setNumber(number+1)을 하는 것이 아니라, 어떻게 업데이트할지 정의
⇒ 두 번째 파라미터로 넣는 배열에 number를 넣지 않아도 됨
▼onToggle, onRemove, onInsert 수정
//App.js
import React, { useRef, useState, useCallback } from 'react';
import TodoTemplate from './componenets/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:1,
text:`할 일 ${i}`,
checked: false,
});
}
return array;
}
const App = () => {
const [todos, setTodos] = useState(createBulkTodos);
const nextId = useRef(4);
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;
💡 실제 프로덕션 모드에서는 개발 모드보다 처리 속도가 훨씬 빠름 프로덕션 모드로 구성하고 싶다면 ⇒
yarn build
yarn global add serve
serve -s build
단점 : 기존 코드를 많이 고쳐야 함
장점 : 상태를 업데이트하는 로직을 모아서 컴포넌트 바깥에 둘 수 있음
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:1,
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 = () => {
// 맨처음 렌더링될 때만 createBulkTodos 함수가 호출됨
const [todos, dispatch] = useReducer(todoReducer, undefined, createBulkTodos);
const nextId = useRef(2501);
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;
'불변성을 지킨다' : 기존의 값을 직접 수정하지 않으면서 새로운 값을 만들어 내는 것
❗ 불변성이 지켜지지 않으면 객체 내부의 값이 새로워져도 바뀐 것을 감지하지 못함
배열 혹은 객체의 구조가 복잡해진다면 불변성 유지가 어려워짐
⇒ immer 라이브러리 사용하면 편하게 작업 가능 (12장)
리스트 관련 컴포넌트 최적화 시, 최적화 해주면 좋은 것
※ 내부 데이터가 100개를 넘지 않거나, 업데이트가 자주 발생하지 않는다면 반드시 해줄 필요는 없음
//TodoList.js
import React from 'react';
import TodoListItem from './TodoListItem';
import './TodoList.scss';
const TodoList = ({ todos, onRemove, onToggle }) => {
return (...);
};
export default React.memo(TodoList);
react-virtualized : 리스트 컴포넌트에서 스크롤되기 전에 보이지 않는 컴포넌트는 렌더링하지 않고 크기만 차지하게끔 함
스크롤되면 해당 위치에서 보여주어야 할 컴포넌트 자연스럽게 렌더링함
yarn add react-virtualized
사전 작업
//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로 설정
⇒ List 컴포넌트가 전달받은 props를 사용하여 자동으로 최적화해 줌
//TodoListItem.js - render
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,
(prevProps, nextProps) => prevProps.todo === nextProps.todo,
);
//TodoListItem.scss
.TodoListItem-virtualized {
& + & {
border-top : 1px solid #dee2e6;
}
&:nth-child(even) {
background: #f8f9fa;
}
}
Q1. 컴포넌트 성능 최적화를 위해 shouldComponentUpdate 라이프 사이클을 사용하는데 함수형 컴포넌트에서는 같은 기능을 위해 어떤 함수를 사용해야 하는가?
Q2. 함수가 계속 만들어지는 상황을 방지하기 위해 useState의 함수형 업데이트 기능을 사용하거나, _______를 사용한다.
Q3. 전개 연산자를 사용하면 깊은 복사가 이루어진다. (O,X)
답 : X
[리액트를 다루는 기술]13장 리액트 라우터로 SPA 개발하기 (0) | 2021.12.27 |
---|---|
[리액트를 다루는 기술] 12장 immer를 사용하여 더 쉽게 불변성 유지하기 (0) | 2021.12.27 |
React와 Express를 사용해 메모장 만들기(Hooks 사용 ver.) (0) | 2021.12.01 |
MEMO 프로젝트 (함수형) - DB 연결 없는 Ver. (0) | 2021.12.01 |
[리액트를 다루는 기술] 9장 컴포넌트 스타일 (0) | 2021.11.15 |