리-렌더링의 발생 조건
- 컴포넌트에서 state가 바뀌었을 때
- 컴포넌트가 내려받은 props가 변경되었을 때
- 부모 컴포넌트가 리-렌더링 된 경우 자식 컴포넌트는 모두
최적화(Optimization) 방법의 종류
- memo(React.memo) : 컴포넌트를 캐싱
- useCallback : 함수를 캐싱
- useMemo : 값을 캐싱
캐싱(Caching)은 데이터나 연산 결과를 임시로 저장하는 메모리나 저장소
- 부모 컴포넌트가 리렌더링될 때 모든 자식컴포넌트의 리렌더링을 방지하는 방법
주의사항
부모 컴포넌트의 state의 변경으로 인해 props가 변경이 일어나면 컴포넌트는 리렌더링된다.
import React, { useState } from "react";
import Box1 from "./components/Box1";
import Box2 from "./components/Box2";
import Box3 from "./components/Box3";
const boxesStyle = {
display: "flex",
marginTop: "10px",
};
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 style={boxesStyle}>
<Box1 />
<Box2 />
<Box3 />
</div>
</>
);
}
export default App;
export default React.memo(Box1);
export default React.memo(Box2);
export default React.memo(Box3);
// 각각의 컴포넌트 export 부분에 React.memo()를 선언 시
// 부모 컴포넌트(App.jsx)에서 state 변화(버튼 클릭)로 리-렌더링 되더라도 하위 컴포넌트들은 리-렌더링되지 않는다.
-인자로 들어오는 함수 자체를 기억(memoization)한다.
1. useCallback 사용하지 않을 때
function App() {
const [count, setCount] = useState(0);
//plus
const onPlusClickEventHandler = (e) => {
setCount((prev) => prev +1)
}
//minus
const onMinusClickEventHandler = (e) => {
setCount((prev) => prev -1);
}
// count를 초기화해주는 함수
const initCount = () => {
setCount(0);
};
console.log("App이 렌더링 되었습니다.")
return (
<>
<h3>카운트 예제입니다!</h3>
<p>현재 카운트 : {count}</p>
<button onClick={onPlusButtonClickHandler}>+</button>
<button onClick={onMinusButtonClickHandler}>-</button>
<div style={boxesStyle}>
//Box1에 props로 initCount 전달
<Box1 initCount={initCount} />
<Box2 />
<Box3 />
</div>
</>
);
}
function Box1({ initCount }) {
console.log("Box1이 렌더링되었습니다.");
const onInitButtonClickHandler = () => {
initCount();
};
return (
<div style={boxStyle}>
<button onClick={onInitButtonClickHandler}>초기화</button>
</div>
);
export default React.memo(Box1);
React.memo를 통해서 Box1.jsx는 메모이제이션을 했는데도 리-렌더링이 되는 이유
- 전달 받은 props의 상태변화가 이루어져 리-렌더링된 것이다.
- props의 상태변화가 이루어진 이유: App.jsx라는 함수형 컴포넌트를 사용하기 때문이다.
- App.jsx라는 함수형 컴포넌트가 리렌더링 되면서 안에 있는 모든 코드를 재시작하기 때문이다.
}
문제점)
+ 버튼
이나, - 버튼
을 누를 때 그리고 초기화
버튼을 누를 때 모두
App
컴포넌트와 Box1
컴포넌트가 리-렌더링 되는 것을 볼 수 있습니다.
이유)
App 함수형 컴포넌트가 리-렌더링될 때마다 initCount
함수도 같이 초기화된다. 그로 인해 props의 상태 변화가 이루어져 Box1에서도 같이 리-렌더링 되는 걸 볼 수 있다.
해결)
initCount 함수에 useCallbak을 사용한다(인자로 들어오는 함수 자체를 기억하게 한다.)
2. useCallback을 사용할 때
//initCount 함수에 useCallback 선언
const initCount = useCallback(() => {
setCount(0);
console.log(`[COUNT 변경] ${count}에서 0으로 변경되었습니다.`);
}, []);
//이 함수의 렌더링은 1번만 실행 후 리-렌더링되지 않는 걸 볼 수 있다.
//증가, 감소, 초기화를 눌러도 함수가 리-렌더링되지 않는 걸 볼 수 있다.
추가적으로 알아두기
//initCount 함수에 useCallback 선언
const initCount = useCallback(() => {
setCount(0);
console.log(`[COUNT 변경] ${count}에서 0으로 변경되었습니다.`);
}, []);
//문제점: 위의 console.log의 count값이 변하지 않고 0으로 고정된 걸 볼 수 있다.
//이유: 함수가 저장된 시점이 처음 렌더링될 시점의 값인 count 0을 메모리에 저장했기 때문이다.
//그 이후 리-렌더링이 되지 않아 값이 고정인 상태이다.
//해결)
//의존성 배열을 사용하여 특정 배열의 값이 변할 경우 렌더링되게 만들면 된다.
//현재는 []배열에서 ==> [count]로 변경하면 된다.
: 동일한 값을 반환하는 함수(반복문)의 처음 해당 값을 메모리에 임시 저장한다.
1. useMemo 예제
const heavyWork = () => {
for(let i=0; i <1000000000; i++){};
return 100;
}
const value = useMemo(() => {
heavyWork()
},[]);
console.log(value);
//해석)
// dependency Array의 값이 변경될 때만 `반환될 함수(heavyWork)`가 호출됩니다.
// 그 외의 경우에는 memoization 해놨던 값을 가져온다.
// 현재 dependency Array의 값은 []로, heavytWork 함수는 첫 렌더링 이후 리-렌더링이 되지 않고 있다.
// 이후에 사용되는 value는 useMemo를 통해 memoization된 값을 사용하는 것이다.
2. useMemo 예제
function ObjectComponent() {
const [isAlive, setIsAlive] = useState(true);
const [uselessCount, setUselessCount] = useState(0);
const me = {
name: "Ted Chang",
age: 21,
isAlive: isAlive ? "생존" : "사망",
};
// 의존성 배열 위치에 me의 값을 넣어 변경될 경우에만 렌더링 되게끔 만들었지만
// count를 증가하는 button을 눌러보면 계속 log가 찍히는 것을 볼 수가 있다.
useEffect(() => {
console.log("생존여부가 바뀔 때만 호출해주세요!");
}, [me]);
// 이유) count의 상태가 변하면서 현재 함수형 컴포넌트가 리-렌더링되어 me의 값 또한 초기화되어 상태가 변경된다
// 그리하여 useEffect가 실행되는 걸 볼 수 있다.
// 해결) me 객체인 값을 useMemo 해줘야한다.(아래처럼)
// 그러면 count 증가 버튼을 눌러도 me 객체의 값이 리-렌더링되지 않아
// useEffect부분이 실행되지 않는 걸 볼 수 있다.
// const me = useMemo(() => {
// return {name: "Ted Chang",
// age: 21,
// isAlive: isAlive ? "생존" : "사망",
// }
// },[isAlive]);
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;
주의사항
useMemo를 남발하게 되면 별도의 메모리 확보를 너무나 많이 하게 되기 때문에 오히려 성능이 악화될 수 있습니다.