🤪 크롬 개발자 도구를 통한 성능 모니터링
- 녹화 버튼을 누르고 함수를 실행시킨 다음 Stop 버튼을 누른다.
- 성능 분석 결과에 나타난 Timings를 열어 본다.
😰 React.memo를 사용하여 컴포넌트 성능 최적화
- props가 바뀌지 않았다면, 리렌더링하지 않도록 설정한다.
- TodoListItem.js의 마지막 부분을 아래처럼 바꾼다.
export default React.memo(TodoListItem)
🧐 onToggle, onRemove 함수가 바뀌지 않게 하기
useState의 함수형 업데이트
- setTodos를 사용할 때 새로운 상태를 파라미터로 넣는 대신, 상태 업데이트를 어떻게 할지 정의해 주는 업데이트 함수를 넣는 것
const [number, setNumber] = useState(0)
const onIncrease = useCallback(
() => setNumber(prevNumber => prevNumber + 1)
, []
)
- 이렇게 하면 두 번째 파라미터로 number를 넣지 않아도 된다.
- 이를 이용해 onInsert 함수를 수정하면 아래와 같다.
function createBulkTodos() {
const array = [];
for (let i=1 ;i<=2500; i++){
array.push({
id: i,
text: `Todos content ${i}`,
checked: false,
})
}
return array;
}
const App = () => {
const [todos, setTodos] = useState(createBulkTodos)
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,
),
)
},[])
}
- setTodos를 사용할 때 그 안에 'todos=>'만 앞에 넣어 주었다.
- useState(createBulkTodos())로 쓰지 않은 이유는 그렇게 해야 컴포넌트가 처음 렌더링 될 때만 createBulkTodos 함수가 실행되기 때문이다.
useReducer를 사용
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, createBulkTodos)
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})
}, [])
}
- 원래는 useReducer의 두 번째 파라미터로 초기 상태를 넣어주어야 한다.
- 하지만 이번엔 두 번째 파라미터로 undefined를 넣고 세 번째 파라미터에 초기 상태를 만들어주는 createBulkTodos를 넣어주었다.
- 이렇게 해야 컴포넌트가 맨 처음 렌더링 될 때만 createBulkTodos 함수가 호출된다.
😍 불변성의 중요성
- 기존의 값을 직접 수정하지 않으면서 새로운 값을 만들어 내는 것을 '불변성을 지킨다'고 한다.
- 상태 업데이트가 필요한 곳에서는 아예 새로운 배열 혹은 새로운 객체를 만들었기 때문에 React.memo를 사용했을 때 props가 바뀌었는지 혹은 바뀌지 않았는지를 알아내서 리렌더링 성능을 최적화해 줄 수 있었다.
- 불변성이 지켜지지 않으면 객체 내부의 값이 새로워져도 바뀐 것을 감지하지 못한다.
- 그러면 React.memo에서 서로 비교하여 최적화하는 것이 불가능하다.
🥰 react-virtualized를 사용한 렌더링 최적화
- pagination과 비슷한 기능
- 화면에 보이지 않는 컴포넌트는 렌더링되지 않고 크기만 차지하게 끔 하는 기술
- yarn add react-virtualized
- 최적화를 수행하려면 사전에 각 항목의 실제 크기를 px 단위로 알아내야 한다.
import {List} from 'react-virtualized';
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}
></TodoListItem>
)
},[onRemove, onToggle, todos])
return (
<List
className="TodoList"
width={512}
height={513}
rowCount={todos.length}
rowHeight={57}
rowRenderer={rowRenderer}
list={todos}
style={{outline: 'none'}}
></List>
)
}
export default React.memo(TodoList);
const TodoListItem = ({todo, onRemove, onToggle, style}) => {
return(
<div className="TodoListItem-virtualized" style={style}>
</div>
)
}
export default React.memo(
TodoListItem,
(prevProps, nextProps) => prevProps.todo === nextProps.todo,
)
.TodoListItem-virtualized{
& + & {
border-top: 1px solid #dee2e6;
}
&:nth-child(even){
background: #f8f9fa;
}
}
😛 꿀팁!!
디버그 모드가 아닌 프로덕션 모드로 테스트하기
- yarn build
- yarn global add serve
- serve -s build