Memoization 📖
리렌더링 발생 조건
- 컴포넌트에서 state가 바뀌었을 때
- 컴포넌트가 내려받은 props가 변경되었을 때
- 부모 컴포넌트가 리렌더링 된 경우 자식 컴포넌트 모두
최적화
리액트에서 리렌더링이 자주 발생한다면 비용적인 측면에서 좋지 않기 때문에 최적화 작업이 필요합니다. 그래서 대표적인 방법으로 아래 3가지 방법이 있습니다.
`React.memo` : 컴포넌트를 캐싱
`useMemo` : 값을 캐싱
`useCallback` : 함수를 캐싱
React.memo📖
1번 컴포넌트가 리렌더링 될 때 2~7번이 모두 리렌더링 됩니다.
4번 컴포넌트가 리렌더링 될 때 6~7번이 모두 리렌더링 됩니다.
이 처럼 자식 컴포넌트에서 바뀐 게 없는데 리렌더링 되는 문제를 `React.memo`로 해결할 수 있습니다.
사용 방법
App.jsx
function App() {
console.log("App 컴포넌트가 렌더링되었습니다!");
const [count, setCount] = useState(0);
// 1을 증가시키는 함수
const onPlusButtonClickHandler = () => {
setCount(count + 1);
};
// 1을 감소시키는 함수
const onMinusButtonClickHandler = () => {
setCount(count - 1);
};
return (
<>
<h3>카운트 예제입니다!</h3>
<p>현재 카운트 : {count}</p>
<button onClick={onPlusButtonClickHandler}>+</button>
<button onClick={onMinusButtonClickHandler}>-</button>
<div>
<Box1 />
</div>
</>
);
}
Box1.jsx
import React from "react";
function Box1() {
console.log("Box1이 렌더링되었습니다.");
return <div>Box1</div>;
}
export default React.memo(Box1);
정말 간단히 return 하는 컴포넌트를 `React.memo`로 감싸주면 됩니다. 부모 컴포넌트의 state 변경으로 인해 props가 변경이 일어나지 않는 한 컴포넌트를 리렌더링 되지 않습니다.
export default React.memo(Box1);
useCallback📖
React.memo는 컴포넌트를 메모이제이션 했다면, useCallback은 인자로 들어오는 함수 자체를 메모이제이션합니다.
만약 Box1 컴포넌트에서 `initCount`메서드를 props 받아와 초기화를 한다면 Box1 컴포넌트를 React.memo로 메모이제이션 해도 리렌더링이 발생하게 됩니다.
// App.jsx
const initCount = () => {
setCount(0);
};
// Box1.jsx
function Box1({ initCount }) {
console.log("Box1이 렌더링되었습니다.");
const onInitButtonClickHandler = () => {
initCount();
};
return (
<div style={boxStyle}>
<button onClick={onInitButtonClickHandler}>초기화</button>
</div>
);
}
❓왜 리렌더링이 발생할까요
함수도 객체의 한 종류이기 때문에 리렌더링이 발생하면 고유한 함수를 생성하기 때문에 주소값이 달라지고 이에 따라 하위 컴포넌트는 props가 변경됐다고 인식하게 되는 겁니다.
사용 방법
// 변경 전
const initCount = () => {
setCount(0);
};
// 변경 후
const initCount = useCallback(() => {
setCount(0);
}, []);
`initCount`를 특정 메모리 공간에 저장해 놓고(=메모이제이션) 의존성 배열에 값이 변경되지 않으면 동일한 함수를 props로 내려주게 됩니다. 그래서 리렌더링이 발생하지 않습니다.
useMemo 📖
`useMemo` 는 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 합니다.
❗간단하게 설명하면
처음 값을 계산해 메모리에 저장하고 필요할 때마다 다시 계산하지 않고 메모리에서 꺼내서 재사용합니다.
사용 방법
// as-is
const value = 반환할_함수();
// to-be
const value = useMemo(()=> {
return 반환할_함수()
}, [dependencyArray]);
의존성 배열의 값이 변경될 때만 `반환할 함수()`가 호출됩니다.
import React, { useState, useMemo } from "react";
function HeavyButton() {
const [count, setCount] = useState(0);
const heavyWork = () => {
for (let i = 0; i < 1000000000; i++) {}
return 100;
};
// CASE 1 : useMemo를 사용하지 않았을 때
// const value = heavyWork();
// CASE 2 : useMemo를 사용했을 때
const value = useMemo(() => heavyWork(), []);
return (
<>
<p>나는 {value}을 가져오는 엄청 무거운 작업을 하는 컴포넌트야!</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
누르면 아래 count가 올라가요!
</button>
<br />
{count}
</>
);
}
export default HeavyButton;
위 코드에서 `useMemo`를 사용하지 않고 버튼을 눌렀을 땐 화면이 리렌더링 되는 속도가 엄청 느리지만 `useMemo`를 사용하면 처음 값을 계산해 메모리에 저장하고 이후에는 메모리에서 꺼내서 재사용하기 때문에 값을 최적화를 할 수 있습니다.
주의 사항
메모이제이션을 남발하게 되면 별도의 메모리 공간 확보를 많이 하기 때문에 오히려 성능이 떨어질 수 있습니다.
필요할 때만 사용하는 것이 좋습니다!
'공부 > react' 카테고리의 다른 글
useSyncExternalStore 훅 알아보기 (1) | 2024.08.28 |
---|---|
라이브러리 없이 라우터(Router) 만들기 (0) | 2024.08.27 |
React는 Hooks를 배열로 관리하는 이유 (0) | 2024.08.19 |
React 숙련 4 (react-router-dom) (0) | 2024.08.16 |
React 숙련 2 (react hooks) (1) | 2024.08.16 |
React 숙련 1 (styled-components) (0) | 2024.08.16 |
React 입문 6 (DOM, VirtualDOM) (0) | 2024.08.09 |
React 입문 5 (명령형, 선언형) (0) | 2024.08.09 |