11.1 많은 데이터 렌더링하기
- useState(createBulkTodos()) 라고 작성하면 리렌더링될 때마다 createBulkTodos 함수가 호출되지만, useState(createBulkTodos)처럼 파라미터를 함수 형태로 넣어 주면 컴포넌트가 처음 렌더링될 때만 createBulkTodos 함수가 실행된다.
11.2 크롬 개발자 도구를 통한 성능 모니터링
- 리액트 v17 부터는 리액트 전용 개발자 도구인 React DevTools를 사용해야 성능 분석을 자세하게 할 수 있다.
- 사용할 항목을 체크한 다음, 리액트 개발자 도구의 Profiler라는 탭에서 파란색 녹화 버튼을 누르면 성능 분석 결과가 나온다.
- Render duration은 리렌더링에 소요된 시간을 의미하며, 1ms는 0.001초 이다.
- Profiler탭의 상단에 있는 불꽃 모양 아이콘 랭크 차트 아이콘은 리렌더링된 컴포넌트를 오래 걸린 순으로 정렬하여 나열해준다.
11.3 느려지는 원인 분석
컴포넌트는 다음과 같은 상황에서 리렌더링이 발생한다.
1. 자신이 전달받은 props가 변경될 때
2. 자신의 state가 바뀔 때
3. 부모 컴포넌트가 리렌더링될 때
4. forceUpdate 함수가 실행될 때
11.4 React.memo를 사용하여 컴포넌트 성능 최적화
- 컴포넌트의 리렌더링을 방지할 때는 shouldComponentUpdate라는 라이프사이클을 사용하면 된다.
- 함수 컴포넌트에서는 라이프사이클 메서드를 사용할 수 없어, 그 대신 React.memo라는 함수를 사용한다.
- 컴포넌트의 props가 바뀌지 않았다면, 리렌더링하지 않도록 설정하여 함수 컴포넌트의 리렌더링 성능을 최적화해 줄 수 있다.
const TodoListItem = ({todo, onRemove, onToggle}) => {
(...)
}
export default React.memo(TodoListItem);
- React.memo의 사용법은 컴포넌트를 만들고 나서 감싸 주기만 하면 된다.
- TodoListItem 컴포넌트는 todo, onRemove, onToggle이 바뀌지 않으면 리렌더링을 하지 않는다.
11.5 onToggle, onRemove 함수가 바뀌지 않게 하기
1️⃣ useState의 함수형 업데이트
- setTodos를 사용할 때 새로운 상태를 파라미터로 넣는 대신, 상태 업데이트를 어떻게 할지 정의해 주는 업데이트 함수를 넣을 수도 있다. 이를 함수형 업데이트라고 부른다.
const [number, setNumber] = useState(0);
// prevNumbers는 현재 number값을 가리킨다.
const onIncrease = useCallback(
() => setNumber(prevNumber => prevNumber + 1), [],
);
- setNumber(number + 1)을 하는 것이 아니라, 위 코드처럼 어떻게 업데이트할지 정의해 주는 업데이트 함수를 넣어 준다.
- useCallback을 사용할 대 두 번째 파라미터로 넣는 배열에 number를 넣지 않아도 된다.
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 => 만 넣어 주면 된다.
- Profiler 개발자 도구의 회색 빗금이 그어져 있는 박스들은 React.memo를 통해 리렌더링되지 않은 컴포넌트를 나타낸다.
2️⃣ useReducer 사용하기
const [todos, dispatch] = useReducer(todoReducer, undefined, createBulkTodos);
// 고윳값으로 사용될 id
// ref를 사용하여 변수 담기
const nextId = useRef(2501);
const onInsert = useCallback(text => {
const todo = {
id: nextId.current,
text,
checked: false,
};
dispatch({type: "Insert", todo});
nextId.current += 1; // nextId 1씩 더하기
},[]);
- useReducer를 사용할 때는 원래 두 번째 파라미터에 초기 상태를 넣어 주어야 한다.
- 그 대신 두 번째 파라미터에 undefined를 넣고 세 번째 파라미터에 초기 상태를 만들어 주는 함수를 넣어준다.
- 이렇게 하면 컴포넌트가 맨 처음 렌더링될 때만 함수가 호출된다.
- useReducer를 사용하는 방법은 기존 코드를 많이 고쳐야 한다는 단점이 있지만, 상태를 업데이트 하는 로직을 모아서 컴포넌트 바깥에 둘 수 있다는 장점이 있다.
11.6 불변성의 중요성
- 리액트 컴포넌트에서 상태를 업데이트할 때 불변성을 지키는 것은 매우 중요하다.
const onToggle = useCallback(id => {
setTodos(todos =>
todos.map(todo =>
todo.id === id ? {...todo, checked: !todo.checked} : todo,
),
)
}, [])
- 기존 데이터를 수정할 때 직접 수정하지 않고 새로운 배열을 만든 다음에 새로운 객체를 만들어서 필요한 부분을 교체해 주는 방식으로 구현한다.
- 업데이트가 필요한 곳에서는 아예 새로운 배열 혹은 새로운 객체를 만들기 때문에, React.memo를 사용했을 때 props가 바뀌었는지 혹은 바뀌지 않았는지를 알아내서 리렌더링 성능을 최적화해 줄 수 있다.
- 이렇게 기존의 값을 직접 수정하지 않으면서 새로운 값을 만들어 내는 것을 "불변성을 지킨다"고 말한다.
- 추가로 전개 연산자(... 문법)를 사용하여 객체나 배열 내부의 값을 복사할 때는 얕은 복사(shallow copy)를 하게 된다.
- 즉, 내부의 값이 완전히 새로 복사되는 것이 아니라 가장 바깥쪽에 있는 값만 복사된다.
- 내부의 값이 객체 혹은 배열이라면 내부의 값 또한 따로 복사해 주어야 한다.
11.7 TodoList 컴포넌트 최적화하기
- 리스트에 관련된 컴포넌트를 최적화할 때는 리스트 내부에서 사용하는 컴포넌트도 최적화해야 하고 리스트로 사용되는 컴포넌트 자체도 최적화해 주는 것이 좋다.
- 현재 프로젝트 성능해 전혀 영향을 주지 않더라도, 컴포넌트에 다른 state가 추가되어 해당 값들이 업데이트될 때는 텀포넌트가 불필요한 리렌더링을 할 수 있다. 그래서 미리 React.memo를 사용해 최적화해줄 수 있다.
- 리스트 관련 컴포넌트를 작성할 때는 리스트 아이템과 리스트, 이 두 가지 컴포넌트를 최적화해주는 것을 잊어서는 안된다.
- 그러나 내부 데이터가 100개를 넘지 않거나 업데이트가 자주 발생하지 않는다면, 이런 최적화 작업을 반드시 해 줄 필요는 없다.
11.8 react-virtualized를 사용한 렌더링 최적화
- react-virtualized를 사용하면 리스트 컴포넌트에서 스크롤되기 전에 보이지 않는 컴포넌트는 렌더링하지 않고 크기만 차지하게끔 할 수 있다.
- 만약 스크롤되면 해당 스크롤 위치에서 보여 주어야 할 컴포넌트를 자연스럽게 렌더링시킨다.
1️⃣ 최적화 준비
- 최적화를 수행하려면 사전에 먼저 해야 하는 작업이 있는데 바로 각 항목의 실체 크기를 px 단위로 알아내는 것이다.
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"}} // List에 기본 적용되는 outline 스타일 제거
/>
)
- react-virtualized의 List컴포넌트에서 각 TodoItem을 렌더링할 때 사용하며, 이 함수를 List 컴포넌트의 props로 설정해 주어야 한다.
- List 컴포넌트를 사용할 때는 해당 리스트의 전체 크기와 각 항목의 높이, 각 항목을 렌더링할 때 사용해야 하는 함수, 그리고 배열을 props로 넣어주어야 한다. 그러면 이 컴포넌트가 전달받은 props를 사용하여 자동으로 최적화해준다.
3️⃣ TodoListItem 수정
return(
<div className="TodoListItem-virtualized" style={style}>
(...)
</div>
)
- render 함수에서 기존에 보여 주던 내용을 div로 한 번 감싸고, 해당 div에는 className을 설정하고 props로 받아 온 style을 적용시켜 준 코드이다.
11.9 정리
- 리액트 컴포넌트의 렌더링은 기본적으로 빠르기 때문에 컴포넌트를 개발할 때 최적화 작업에 대해 너무 큰 스트레스를 받거나 모든 컴포넌트에 일일이 React.memo를 작성할 필요는 없다.
- 단, 리스트와 관련된 컴포넌트를 만들 때 보여 줄 항목이 100개 이상이고 업데이트가 자주 발생한다면, 꼭 최적화하는 것이 좋다.
#출처 : 리액트를 다루는 기술(저자 : 김민준 VELOPERT)
'Front-End, CS 스터디 > [도서] 리액트를 다루는 기술' 카테고리의 다른 글
[도서] 리액트를 다루는 기술 #JSX (0) | 2022.11.11 |
---|---|
[도서] 리액트를 다루는 기술 #컴포넌트의 라이프사이클 메서드 (0) | 2022.11.09 |
[도서] 리액트를 다루는 기술 #Hooks (0) | 2022.11.05 |
[도서] 리액트를 다루는 기술 #서버 사이드 렌더링 (2) | 2022.11.01 |
[도서] 리액트를 다루는 기술 #컴포넌트 반복 (0) | 2022.10.28 |