리액트에서 리렌더링이 빈번하게 자주 일어난다는 것은 비용이 늘어난다는 것이므로 이를 최대한 줄여야 하는데, 이런 작업을
최적화(Optimization)
라고 함🔽 불필요한 렌더링이 발생하지 않도록 최적화 하는 작업들 🔽
- React.memo : 컴포넌트를 캐싱
- useCallback : 함수를 캐싱
- useMemo : 값을 캐싱
React.memo : 부모 컴포넌트에 따라 불필요하게 리렌더링 되는 자녀 컴포넌트가 없도록 도움
💥 부모컴포넌트에 있는 카운트가 변하면 자식 컴포넌트(Box1,2,3)도 리렌더링 되는 상황
💡 자식 컴포넌트에 React.memo()
를 씌움
export default React.memo(Box1);
export default React.memo(Box2);
export default React.memo(Box3);
처음 렌더링 된 후 부모 컴포넌트만 리렌더링
React.memo가 컴포넌트를 memoization 했다면,
useCallback은 인자로 들어오는 함수 자체를 기억(memoization)함
🤔 React.memo를 통해서 Box를 메모이제이션 했는데도 리렌더링 되는 이유?
💡 함수형 컴포넌트를 사용했기 때문에 App.jsx가 리렌더링 되면서 코드가 다시 만들어짐
(함수도 객체의 한 종류이기 때문에 모양은 같더라도 다시 만들어지면 주소값이 달라지고 이에 따라 하위 컴포넌트인 Box1 컴포넌트
는 props가 변경됐다고 인식)
함수를 메모리 공간에 저장해놓고 특정 조건이 아닌 경우 변경되지 않도록 함
// 변경 전
const initCount = () => {
setCount(0);
};
// 변경 후
const initCount = useCallback(() => {
setCount(0);
}, []);
count를 초기화할 때 콘솔을 찍어보면 예상치 못한 결과가 나옴
(0에서 0으로 변경되었습니다)
💡 useCallback
이 count
가 0일 때의 시점을 기준으로 메모리에 함수를 저장했기 때문에 이를 막기위해 dependency array
가 필요
// count를 초기화해주는 함수
const initCount = useCallback(() => {
console.log(`[COUNT 변경] ${count}에서 0으로 변경되었습니다.`);
setCount(0);
}, [count]);
동일한 값을 반환하는 함수를 계혹 호출할 때 생기는 필요없는 렌더링을 줄이기 위해 처음 값을 반환할 때 특별한 곳(메모리)에 저장함
👉 필요할 때마다 다시 함수를 호출하는 게 아닌 이미 저장한 값을 꺼내와서 재사용 가능(=캐싱)
dependencyArray
의 값이 변경될 때만 반환할_함수()
가 호출됨
// 변경 전
const value = 반환할_함수();
// 변경 후
const value = useMemo(()=> {
return 반환할_함수()
}, [dependencyArray]);
💡 heavyWork()
의 반환 값을 저장해서 필요할 때만 꺼내오면 불필요한 렌더링을 줄일 수 있음
// app.jsx
import "./App.css";
import HeavyComponent from "./components/HeavyComponent";
function App() {
return (
<>
<HeavyComponent />
</>
);
}
export default App;
// HeavyComponent.jsx
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;
// uselessCount를 바꾸면 생존여부가 바뀌지 않았음에도 계속해서 console.log가 찍힘
import React, { useEffect, useState } from "react";
function ObjectComponent() {
const [isAlive, setIsAlive] = useState(true);
const [uselessCount, setUselessCount] = useState(0);
const me = {
name: "Ted Chang",
age: 21,
isAlive: isAlive ? "생존" : "사망",
};
useEffect(() => {
console.log("생존여부가 바뀔 때만 호출해주세요!");
}, [me]);
return (
<>
<div>
내 이름은 {me.name}이구, 나이는 {me.age}야!
</div>
<br />
<div>
<button
onClick={() => {
setIsAlive(!isAlive);
}}
>
누르면 살았다가 죽었다가 해요
</button>
<br />
생존여부 : {me.isAlive}
</div>
<hr />
필요없는 숫자 영역이에요!
<br />
{uselessCount}
<br />
<button
onClick={() => {
setUselessCount(uselessCount + 1);
}}
>
누르면 숫자가 올라가요
</button>
</>
);
}
export default ObjectComponent;
🤔 왜 그럴까?
💡 불변성과 관련
1. 버튼이 클릭되어 uselessCount의 state 바뀌면 리렌더링이 되고
2. 컴포넌트 함수가 새롭게 호출됨
3. me 객체도 다시 할당됨 (이전과 다른 메모리 주소값)
4. useEffect의 dependency array에 의해 내부 로직 호출
(새로운 메모리 주소값 때문에 바뀐 걸로 인식)
// useMEmo로 해결
const me = useMemo(() => {
return {
name: "Ted Chang",
age: 21,
isAlive: isAlive ? "생존" : "사망",
};
}, [isAlive]);
useMemo를 남발하게 되면 별도의 메모리 확보를 너무 많이 하게 되어 오히려 성능이 악화될 수 있으므로 필요할 때만 사용!