1. 크롬 개발자 도구를 통한 성능 모니터링
→ 리액트 v17부터는 리액트 전용 개발자 도구인 ReactDevTools를 사용하여 자세한 성능 분석이 가능하다.
- 리액트 개발자 도구의 Profiler 탭 클릭 후
- 좌측 상단의 파란색 녹화 버튼 클릭
- 성능 분석하고 싶은 작업 실행
- 작업 완료 후, 파란색 녹화 버튼을 다시 한번 클릭하면 성능 분석 결과가 나타남
- Render duration: 리렌더링에 소요된 시간. **소요 시간은 컴퓨터 환경에 따라 다를 수 있음
- Profiler 탭 상단에 있는 랭크 차트 아이콘을 클릭하면,
리렌더링된 컴포넌트를 오래 걸린 순으로 정렬하여 나열해 줌
3. 느려지는 원인 분석
- 컴포넌트가 리렌더링 되는 상황
- 자신이 전달 받은 props가 변경될 때
- 자신의 state가 바뀔 때
- 부모 컴포넌트가 리렌더링될 때
- forceUpdate 함수가 실행될 때
→ 리렌더링이 불필요할 때는 리렌더링을 방지해 주어야 한다!
4. React.memo를 사용하여 컴포넌트 성능 최적화
→ 컴포넌트의 props가 바뀌지 않았다면, 리렌더링하지 않도록 설정하여 함수 컴포넌트의 리렌더링 성능을 최적화!
- 컴포넌트의 리렌더링을 방지하는 방법
- 클래스 컴포넌트: shouldComponentUpdate 라이프사이클 메서드 사용
- 함수형 컴포넌트: React.memo 함수 사용
import React from 'react';
const TodoListItem= ({ todo, onRemove, onToggle }) => {
(...)
};
export default React.memo(TodoListItem);
//컴포넌트를 만들고 나서 'React.memo'로 감싸주기만 하면 된다!
- 위와 같이 React.memo로 감싸주면 TodoListItem 컴포넌트는 todo, onRemove, onToggle이 바뀔 때만 리렌더링 된다!
5. onToggle, onRemove 함수가 바뀌지 않게 하기
- 함수가 계속 만들어지는 상황을 방지하는 방법
- useState의 함수형 업데이트 기능을 사용하는 방법
- useReducer를 사용하는 방법
▶ useState의 함수형 업데이트
→ setState 함수(useState의 setter 함수)를 사용할 때 새로운 상태를 파라미터로 넣는 대신, 상태 업데이트를 어떻게 할지 정의해 주는 업데이트 함수를 넣어주는 것을 함수형 업데이트라고 한다.
더보기
▽ setTodos를 사용할 때 그 안에 todos => 만 앞에 넣어 주면 된다!
// App.js
import React 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;
}
const App = () => {
const [todos, setTodos] = React.useState(createBulkTodos);
//고유값으로 사용될 id
const nextId = React.useRef(4);
const onInsert = React.useCallback(text => {
const todo = { id: nextId.current, text, checked: false };
setTodos(todos => todos.concat(todo));
nextId.current += 1; // nextId 1씩 증가 시키기
},[]);
const onRemove = React.useCallback(id => {
setTodos(todos => todos.filter(todo => todo.id !== id));
},[]);
const onToggle = React.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;
▶ useReducer 사용하기
더보기
// App.js
import React 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': // 새로 추가
// { 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: 'REMOVE', id: 1 }
return todos.map(todo =>
todo.id === action.id ? { ...todo, checked: !todo.checked } : todo
);
default:
return todos;
}
}
const App = () => {
const [todos, dispatch] = React.useReducer(todoReducer, undefined, createBulkTodos);
//고유값으로 사용될 id
const nextId = React.useRef(2501);
const onInsert = React.useCallback(text => {
const todo = { id: nextId.current, text, checked: false };
dispatch({ type: 'INSERT', todo });
nextId.current += 1; // nextId 1씩 증가 시키기
},[]);
const onRemove = React.useCallback(id => {
dispatch({ type: 'REMOVE', id });
},[]);
const onToggle = React.useCallback(id => {
dispatch({ type: 'TOGGLE', id });
},[]);
return (
<TodoTemplate>
<TodoInsert onInsert={onInsert} />
<TodoList todos={todos} onRemove={onRemove} onToggle={onToggle}/>
</TodoTemplate>
)
}
export default App;
6. 불변성의 중요성
- 기존의 값을 직접 수정하지 않으면서 새로운 값을 만들어 내는 것을 '불변성을 지킨다'고 한다
- 불변성이 지켜지지 않으면 객체 내부의 값이 새로워져도 바뀐 것을 감지하지 못한다
→ React.memo에서 서로 비교하여 최적화 하는 것이 불가능! - 전개 연산자(... 문법)를 사용하여 객체나 배열 내부의 값을 복사할 때는 얕은 복사(shallow copy)를 한다.
즉, 내부의 값이 완전히 새로 복사되는 것이 아니라 가장 바깥쪽의 값만 복사된다.
그러므로, 내부의 값이 객체 혹은 배열이라면 내부의 값 또한 따로 복사를 해주어야 한다!
→ 배열 혹은 객체의 구조가 복잡해질수록 불변성을 유지하면서 업데이트하는 것이 까다로워진다.
이렇게 복잡한 상황에서는 immer라는 라이브러리의 도움을 받으면 편하게 작업할 수 있다.
7. TodoList 컴포넌트 최적화하기
→ 리스트에 관련된 컴포넌트를 최적화할 때는 리스트 내부에서 사용하는 컴포넌트와 리스트로 사용되는 컴포넌트 모두 최적화해야 한다.
// TodoList.js
import React from 'react';
import TodoListItem from './TodoListItem';
import './TodoList.scss';
const TodoList = ({ todos, onRemove, onToggle }) => {
return (...);
}
export default React.memo(TodoList);
- 상기의 코드와 같이 코드를 짤 경우, props를 지정해 주었으므로 불필요한 리렌더링이 발생하지 않는다.
- App 컴포넌트에 다른 state가 추가되어, 그로 인한 리렌더링이 발생할 경우를 대비하여 React.memo를 사용해서 최적화한 코드!
8. react-virtualized를 사용한 렌더링 최적화
- 리액트 컴포넌트 리렌더링 성능을 최적화하는 방법
- 필요할 때만 리렌더링!
- 화면에 보이지 않는 정보를 미리 렌더링하지 않고, 보여져야할 때 렌더링 하도록 설정
- 2번 방법은 react-virtualized 라이브러리를 사용하여 쉽게 구현할 수 있다.
- yarn을 사용하여 라이브러리 설치: yarn add react-virtualized
- TodoList 수정 ← 하기 전에, 각 항목의 실제 크기를 px 단위로 알아야 한다! (그 수치를 List에 적용해야 함)
// TodoList.js
import React from 'react';
import { List } from 'react-virtualized';
import TodoListItem from './TodoListItem';
import './TodoList.scss';
const TodoList = ({ todos, onRemove, onToggle }) => {
const rowRender = React.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} // 항목 높이
rowRender={rowRender} // 항목을 렌더링할 때 쓰는 함수
list={todos} // 배열
style={{ outline: 'none' }} // List에 기본 적용되는 outline 스타일 제거
/>
)
};
export default React.memo(TodoList);
9. 정리
리액트 컴포넌트의 렌더링은 기본적으로 빠르기 때문에 개발할 때 최적화 작업에 너무 큰 스트레스를 받거나 모든 컴포넌트에 React.memo를 작성할 필요는 없다.
다만, 리스트와 관련된 컴포넌트를 만들 때 보여줄 항목이 100개 이상이고 업데이트가 잦다면 최적화할 필요가 있다!
참고자료
- 리액트를 다루는 기술 [김민준 저]
'개발 > React' 카테고리의 다른 글
[리액트를 다루는 기술] 리덕스 미들웨어를 통한 비동기 작업 관리 (0) | 2023.04.08 |
---|---|
[리액트를 다루는 기술] Context API (0) | 2023.03.26 |
[리액트를 다루는 기술] Hooks_useState, useEffect (0) | 2023.03.12 |
[리액트를 다루는 기술] LifeCycle Method (0) | 2023.02.26 |
[리액트를 다루는 기술] ref: DOM에 이름 달기 (0) | 2023.02.20 |