useEffect(someSideEffect, [deps]);
useEffect는 sideEffect를 처리하는 콜백과 의존성 배열을 입력으로 받는다. 이때 의존성 배열에 존재하는 참조변수가 변경되면 해당 콜백이 다시 실행되는 형태를 가진다. 즉, useEffect는 의존성 배열 내에 존재하는 참조변수의 변경이 일어나지 않으면 콜백을 다시 실행하지 않는다.
이 점을 잘 기억하자.
일반적으로 컴포넌트가 재렌더링되면 컴포넌트 내에 존재하는 변수 및 함수들은 재평가되기 때문에 다시 실행된다. 만약 특정 데이터를 생성해 내기 위해 오랜 시간이 걸리거나 많은 메모리를 사용한다면 어떻게 될까? 분명 비효율적일 것이다. 리액트는 이를 위해 과거에 계산한 값을 반복해서 사용할 수 있도록 useMemo
를 통해 값을 캐시하는 방법을 제공한다.
import { useMemo } from 'react';
const cachedData = useMemo(callback, [deps])
기본적인 방법은 useEffect와 동일하다. 훅에 callback과 의존성 배열을 넘기는 방식이다.
아래 예시를 통해 살펴보자.
// App.js
import { useState } from "react";
import Child from "./Child";
import "./styles.css";
export default function App() {
const [title, setTitle] = useState("제목");
const [dataList, setDataList] = useState([1, 2, 3, 4, 5]);
return (
<div className="App">
<button onClick={() => setTitle("새로운 제목")}>제목 변경</button>
<Child title={title} dataList={dataList} />
</div>
);
}
// Child.js
const Child = ({ title, dataList }) => {
const newDataList = dataList.map((e) => {
console.log(e);
return e * 10;
});
return (
<div>
<h2>{title}</h2>
{newDataList.map((d, idx) => (
<div key={idx}>{d}</div>
))}
</div>
);
};
export default Child;
위 코드는 부모 컴포넌트로부터 title과 dataList를 받는 Child 컴포넌트이다. Child 컴포넌트는 부모로부터 전달받은 title과 dataList를 사용하는데, dataList는 그냥 출력하는게 아니라, dataList를 통해 특정 작업을 거친 newDataList를 생성해 사용한다는 것이다.
만약 title이나 dataList prop이 변경된다면 Child 컴포넌트는 재렌더링이 발생할 것이고, 결과적으로 newDataList는 다시 생성될 것이다.
newDataList를 생성하는 로직이 복잡하다고 가정해 보자. 이때 title이 변경되면 어떻게 될까?
newDataList는 데이터의 변경이 일어나지 않았지만 Child 컴포넌트에 의해 모든 연산이 다시 진행될 것이다. 리액트는 이를 위해 useMemo
를 제공하여 함수의 결과를 캐시할 수 있다.
위코드에 useMemo를 적용하면 아래와 같다.
// Child.js
import { useMemo } from "react";
const Child = ({ title, dataList }) => {
// newDataList가 매 렌더링마다 재실행될 필요는 없기 때문에 useMemo 사용
const newDataList = useMemo(() => {
console.log("-----dataList-----");
return dataList.map((e) => {
console.log(e);
return e * 10;
});
}, [dataList]); // dataList가 변경될 때만 useMemo 내 콜백 실행하여 값을 갱신(useEffect 생각)
return (
<div>
<h2>{title}</h2>
{newDataList.map((d, idx) => (
<div key={idx}>{d}</div>
))}
</div>
);
};
export default Child;
위 코드에서 변경된 부분은 newDataList를 생성하기 위해 사용한 함수를 useMemo
로 감싼 뒤 dataList를 의존성 배열로 추가한 것 뿐이다. useEffect에서 특정 변수를 참조하는 의존성 배열과 동일한 기능을 한다. 위에서는 dataList가 변경될 때만 useMemo 내에 있는 콜백 함수가 실행되어 새롭게 캐시한다. 즉, dataList가 변경되지 않으면 newDataList는 재렌더링될 때 새로운 연산이 아닌 리액트가 캐시한 데이터를 받게 된다.
useMemo는 만능이 아니다. useMemo를 통해 캐시한 데이터는 리액트가 메모리에서 관리하는 데이터이기 때문에 적절한 트레이드오프를 통해 캐시가 필요한 데이터만 useMemo를 사용하도록 하자.