많은 데이터를 렌더링 할 때 느려짐. lagging
'할 일1'을 체크하면 App 컴포넌트의 state가 변경되면서 리렌더링 됨
부모 컴포넌트(App)가 리렌더링 되었으니 TodoList 컴포넌트도 리렌더링 되고 그 안의 무수한 컴포넌트들도 리렌더링 됨
'할 일1'은 리렌더링 되어야 하지만 나머지들은 안해도 됨
이를 최적화 하는 작업이 필요
불필요한 리렌더링을 방지하는 방법을 알아보자
// TodoListItem.js
const TodoListItem = ({ todo, onRemove, onToggle }) => {
(…)
};
export default React.memo(TodoListItem);
이제 TodoListItem 컴포넌트는 todo, onRemove, onToggle 이 바뀌지 않으면 리렌더링 하지 않음
이것 방지하기 위해서는
setNumbers(number+1)을 하는 것이 아니라
const onIncrease = useCallback(() => {
setNumber(prevNumber => prevNumber + 1), [])
위 코드처럼 어떻게 업데이트 할 지 정의해주는 업데이트 함수 넣어 줌
그러면 useCallback 사용 시 두번째 파라미터로 넣는 배열에 number 안넣어도 됨.
// setTodos를 사용할 때 todos =>만 앞에 넣어 주면 됨
const onRemove = useCallback(id => {
setTodos(todos => todos.filter(todo => todo.id !== id));
}, [],);
// App.js
import React, {useReducer, 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: i,
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 = () => {
const [todos, dispatch] = useReducer(todoReducer, undefined, createBulkTodos)
const nextId = useRef(2501); // 고유값으로 사용될 id. ref 사용해 변수 담기
const onInsert = useCallback(text => {
const todo = {
id: nextId.current,
text,
checked: false,
};
dispatch({type: 'INSERT', todo});
nextId.current += 1; // nextId 1 씩 더하기
}, []);
const onRemove = useCallback(id => {
dispatch({type: 'REMOVE', id});
}, [],);
// 수정 기능
const onToggle = useCallback(id => {
dispatch({type: 'TOGGLE', id});
}, []);
return (
<TodoTemplate>
<TodoInsert onInsert = {onInsert}/> {/*onInsert 함수를 TodoInsert 컴포넌트의 props로 설정*/}
<TodoList todos = {todos} onRemove = {onRemove} onToggle = {onToggle}/> {/* onRemove를 TodoList의 props로 설정 */}
</TodoTemplate>
);
}
export default App;
useReducer 사용시 원래 두번째 파라미터에 초기 상태 넣어 줘야 함
지금은 두번째 파라미터에 undefined 넣고, 세번째에 초기상태 만들어주는 createBulkTodos 넣었다
이렇게 하면 컴포넌트가 맨 ㅍ처은 렌더링될 때만 createBulkTodos함수 호출됨.
useReducer는 상태 어베이트 하는 로직 모아서 컴포넌트 밖에 둘 수 있다는 장점.
둘 중 취향따라 선택. 성능은 비슷
앞서 useState 사용해 만든 onToggle 함수를 보면 기존 데이터 수정 시 직접 수정X, 새로운 배열 만들고 필요 부분 교체
업데이트 필요한 곳에서는 아예 새로운 객체 만들기 때문에
React.memo 사용했을 때 props가 바꿔었는지 알아내서 리렌더링 성능 최적화 할 수 있다.
불변성 지켜지지 않으면 값 바뀌어도 감지 못함
=> React.memo에서 서로 비교해 최적화하는것 불가능
배열/객체의 구조가 복잡해지면 불변성 유지하며 업데이트 하기 까다로움
이런 경우 immer 라는 라이브러리 사용시 용이
리스트 관련 컴포넌트 최적화 시에는 해당 컴포넌트, 리스트로 사용되는 컴포넌트 자체도 최적화 해줘야 함
리스트 아이템과 리스트, 두가지 컴포넌트를 최적화 해 주는것 잊지 말기
지금까지는 리액트 컴포넌트 리렌더링 성능 최적화하는 방법 알아봤음
ㄴ 최적화시 필요할 때만 리렌더링 하도록.
react-virtualized 사용하면 스크롤되기 전 안보이는 컴포넌트는 렌더링 하지 않고 크기만 차지하게끔 해줌.
스크롤 되면 그때 렌더링