최적화
리액트에서의 최적화 기법은 불필요한 연산을 줄여 성능을 향상시키는 것입니다.
리액트 공식 문서에서는 성능 최적화를 위한 방법을 다음과 같이 소개합니다.
최적화 방법
- 코드, 폰트, 이미지 등의 리소스를 압축합니다.
- 메모이제이션을 이용하여 불필요한 연산을 줄입니다.
메모이제이션
메모이제이션이란 연산의 결과를 기억했다가 필요할 때 사용함으로써 불필요한 연산을 방지하는 것입니다.
- useMemo : 함수의 불필요한 재실행을 방지합니다.
- React.memo : 컴포넌트의 불필요한 리렌더링을 방지합니다.
- useCallback : 함수의 불필요한 재생성을 방지합니다.
useMemo
사용하기
문법 : useMemo(() => 연산 결과, [의존성 배열])
useMemo
는 React에서 값을 메모이제이션(caching)하여 성능을 최적화하기 위한 훅입니다.
메모(캐싱)된 값은 의존성 배열에 있는 값이 변경될 때만 다시 계산되므로 복잡한 연산을 계속 반복하지 않고, 이전에 계산한 값을 재사용할 수 있습니다.
- filteredTodo 함수 최적화하기
할 일 관리 앱 만들기 글의 예제를 기반으로 최적화를 진행해보겠습니다.
아래 예제에서 filteredTodo 함수가 빈번하게 호출되어 불필요한 연산이 발생합니다. 테스트를 위해 filteredTodo 함수에 로깅을 추가하고, 콘솔을 확인해보시면 빈번한 호출이 발생하는 것을 확인할 수 있습니다.
// src/components/TodoList.js
(...)
function TodoList() {
(...)
const filteredTodo = () => {
return todos.filter((item) => item.task.toLowerCase().includes(search.toLowerCase()));
};
// lookBack 함수가 빈번하게 호출됩니다.
const lookBack = () => {
console.log('lookBack')
const total = todos.length
const done = todos.filter((todo) => todo.isDone).length
const left = total - done
return { total, done, left }
}
return (
<div>
<h3>할 일 목록 📃</h3>
<input type='text' placeholder="검색어를 입력하세요" onChange={onChangeSearch} value={search} />
<div>
{filteredTodo().map((item) => (
<TodoItem key={item.id} {...item} />
))}
</div>
<div>
{lookBack().total}개 중에 {lookBack().done}개 완료, {lookBack().left}개 남음
</div>
</div>
);
}
export default TodoList;
useMemo를 이용하여 할 일 목록 렌더링 최적화하기
useMemo를 이용하여 filteredTodo 함수의 결과를 기억합니다. useMemo를 사용함으로써 filteredTodo는 함수가 아니라 해당 값을 계산한 결과인 배열을 반환합니다.
// src/component/TodoList.js
import React, { useState, useMemo } from 'react'
import TodoItem from './TodoItem'
export default function TodoList({ todo, onUpdate, onDelete }) {
(...)
// useMemo를 이용하여 filteredTodo 함수의 결과를 기억합니다.
// useMemo를 사용함으로써 filteredTodo는 함수가 아니라 해당 값을 계산한 결과인 배열을 반환합니다.
const filteredTodo = useMemo(() => {
return todo.filter((item) => item.task.toLowerCase().includes(search.toLowerCase()));
}, [search, todo]); // 의존성 배열에 search와 todo를 추가합니다.
// 최적화 테스트
const lookBack = useMemo(() => {
console.log('lookBack')
const total = todos.length
const done = todos.filter((item) => item.isDone).length
const left = total - done
return { total, done, left }
}, [todos]) // 의존성 배열에 todo를 추가합니다.
return (
<div>
(...)
<ul>
// filteredTodo useMemo를 적용한 경우, filteredTodo 자체가 함수가 아니라 해당 값을 계산한 결과인 객체를 반환하기 때문에 ()를 제거합니다.
{filteredTodo.map((item) => (
<TodoItem key={item.id} onUpdate={onUpdate} onDelete={onDelete} {...item} />
))}
</ul>
<div>
// lookBack에 useMemo를 적용한 경우, lookBack 자체가 함수가 아니라 해당 값을 계산한 결과인 객체를 반환하기 때문에 ()를 제거합니다.
{lookBack.total}개 중에 {lookBack.done}개 완료, {lookBack.left}개 남음
</div>
</div>
)
}
useCallback
사용하기
문법 : useCallback(() => 함수, [의존성 배열])
- useCallback가 필요한 상황
- 자식 컴포넌트로 함수를 전달해야 할 때
부모 컴포넌트가 리렌더링되면 함수가 새로 생성되어 자식 컴포넌트가 다시 렌더링될 수 있다.
useCallback을 사용하면 함수 참조값을 유지하여 이러한 문제를 방지.
- 함수가 자주 생성되어 성능 문제가 발생할 때
복잡한 연산을 포함한 함수가 렌더링마다 새로 생성되면 성능에 영향을 줄 수 있다.
useCallback으로 해당 함수를 캐싱하여 성능 문제를 해결.
- onChangeSearch
// src/component/TodoList.js
const onChangeSearch = e => {
setSearch(e.target.value);
};
useCallback을 이용하여 onChangeSearch 함수를 기억하고, 의존성 배열에 추가합니다.
// src/component/TodoList.js
const onChangeSearch = useCallback(e => {
setSearch(e.target.value);
}, []);
- onUpdate, onDelete
함수 재생성 문제
onUpdate
와 onDelete
를 그대로 TodoItem
에 전달하면, TodoList
가 리렌더링될 때마다 새로운 함수 참조가 생성돼 모든 TodoItem
이 다시 렌더링된다.
React는 컴포넌트가 받는 props
가 변경되었는지 확인할 때, 객체나 함수는 참조값이 변경되었는지를 기준으로 비교하기 때문에, 같은 로직이라도 새롭게 생성된 함수는 다른 것으로 간주돼 TodoItem
이 리렌더링된다.
useCallback을 이용한 함수 재사용
useCallback
으로 handleUpdate
와 handleDelete
를 생성하면, 의존성 배열([onUpdate, onDelete
])이 변경되지 않는 한 항상 같은 참조값을 가진 함수를 사용한다. 따라서 React.memo
로 감싼 TodoItem
은 불필요한 리렌더링을 방지할 수 있다.
// src/components/TodoList.js
<TodoItem key={todo.id} onUpdate={onUpdate} onDelete={onDelete} {...todo} />
// src/components/TodoList.js
const handleUpdate = useCallback((id) => onUpdate(id), [onUpdate]);
const handleDelete = useCallback((id) => onDelete(id), [onDelete]);
<TodoItem
key={todo.id}
onUpdate={() => handleUpdate(todo.id)}
onDelete={() => handleDelete(todo.id)}
{...todo}
/>
- useMemo와 useCallback의 차이점
- useMemo: 값을 기억하여 불필요한 연산을 방지합니다.
- useCallback: 함수를 기억하여 불필요한 재생성을 방지합니다.
filterTodo 함수는 값이 변경될 때마다 새로 계산되어야 하므로 useMemo를 사용하고, onChangeSearch 함수는 값이 변경되지 않으므로 useCallback을 사용합니다.
React.memo
사용하기
문법 : React.memo(컴포넌트)
- React.memo를 이용하여 컴포넌트 최적화하기
props
가 변경되지 않으면 리렌더링을 방지하는 React.memo를 사용하여 컴포넌트를 최적화합니다.
React.memo를 사용하여 동일한 props가 전달되면 컴포넌트 리렌더링을 방지합니다.
// src/components/TodoItem.js
import React from 'react';
// React.memo를 사용하여 동일한 props가 전달되면 컴포넌트 리렌더링을 방지.
const TodoItem = React.memo(({ id, isDone, task, createdDate, onUpdate, onDelete }) => {
console.log(`TodoItem ${task}: isDone = ${isDone}`); // 추가
return (
<div>
<li key={id}>
<input
type="checkbox"
checked={isDone}
onChange={onUpdate} // 부모에서 전달받은 핸들러 호출
/>
<span className={`${isDone ? 'line-through text-gray-400' : 'no-underline text-black'}`}>
{task}
</span>
<span>{new Date(createdDate).toLocaleDateString()}</span>
<button onClick={() => onDelete(id)}>삭제</button>
</li>
</div>
);
});
// displayName 추가
TodoItem.displayName = 'TodoItem';
export default TodoItem;
'Front > React' 카테고리의 다른 글
[Hooks] useContext - React 배우기 (0) | 2024.03.20 |
---|---|
[Hooks] useReducer - React 배우기 (0) | 2024.03.19 |
React를 이용한 todo list app 만들기 (0) | 2024.03.14 |
useEffect, 생명주기 - React 배우기 (0) | 2024.03.11 |
React 카운터 앱 만들기 - component, useState (1) | 2024.03.04 |