
부모 컴포넌트가 바뀌면 자식 컴포넌트도 리렌더링이 일어난다. 리액트에서 리렌더링이 자주 일어난다는 것은 cost가 많이 발생한다는 의미이다. 불필요한 렌더링을 최대한 줄이기 위한 방법이 바로 최적화(Optimization)이다.
컴포넌트 memoization
React.memo를 통해 컴포넌트 자체를 메모리에 저장(캐싱)해두고 필요할 때 가져다 쓰는 것이다. 부모 컴포넌트의 state 변경으로 인해 props의 변경이 일어날 때에만 리렌더링이 된다.
자식 컴포넌트를 export default할 때 React.memo를 사용한다.
export default React.memo(자식컴포넌트);
memoization이 되어있는데도 불구하고 리렌더링이 되는 이유는?
자바스크립트에서 함수도 객체의 한 종류이기 때문에 불변성이 없고, 같은 값이어도 별도의 공간에 저장하여 새로운 주소값을 바라보게 된다.
App 컴포넌트가 리렌더링될 때 initCount도 다시 만들어지고, initCount의 모양이 같더라도 주소값이 달라지므로,
Box1.jsx는 props가 바뀌었다고 인지하여 Box1 컴포넌트도 리렌더링이 일어나게 된다.
useCallback(콜백함수, [dependency Array]) 사용useCallback으로 함수를 memorization하여 특정 조건에서만 리렌더링이 되도록 할 수 있다. 이 때 의존성 배열(dependency array)에 count를 넣어주면, count가 변경될 때마다 새롭게 함수를 할당한다.
const [count, setCount] = useState(0);
// count를 초기화해주는 함수
const initCount = useCallback(() => {
console.log(`${count}에서 0으로 변경되었습니다.`);
setCount(0);
}, [count]);
// initCount 함수를 Box1에 props로 내려주는 부분
return (
<>
<h3>카운트 예제입니다!</h3>
<p>현재 카운트 : {count}</p>
<button onClick={onPlusButtonClickHandler}>+</button>
<button onClick={onMinusButtonClickHandler}>-</button>
<div style={boxesStyle}>
<Box1 initCount={initCount} />
<Box2 />
<Box3 />
</div>
</>
);
}
...
...
// initCount 함수를 props로 받아옴
function Box1({ initCount }) {
const onInitButtonClickHandler = () => {
initCount();
};
return (
<div style={boxStyle}>
<button onClick={onInitButtonClickHandler}>초기화</button>
</div>
);
}
...
특정 value값을 memoization
동일한 값을 반환하는 함수를 계속해서 호출해야하는 불필요한 렌더링을 방지하기 위해 그 값을 메모리에 저장한 후, 다시 함수를 호출하지 않고 값을 단순히 꺼내와서 사용할 수 있다. 이러한 기법을 ‘캐싱을 한다’고 표현한다.
useMemo(() ⇒ {return 반환할 함수()}, [dependency Array])
dependency Array의 값이 변경될 때에만 반환할 함수가 호출된다.
const value = useMemo(() => heavyWork(), []);
예를 들어 엄청 무거운 작업이 수행되는 heavyWork 함수가 있다면, value값을 세팅해 주고, useMemo()로 감싸주면 값이 바뀔 때에만 호출된다.
import React, { useState, useMemo } from "react";
function HeavyButton() {
const [count, setCount] = useState(0);
const heavyWork = () => {
for (let i = 0; i < 1000000000; i++) {}
return 100;
};
const value = useMemo(() => heavyWork(), []);
return (
<>
<p>나는 {value}을 가져오는 엄청 무거운 작업 컴포넌트야</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
누르면 아래 count가 올라가요!
</button>
<br />
{count}
</>
);
}
export default HeavyButton;
useMemo를 남발하면 메모리를 너무 많이 확보하기 때문에 오히려 성능이 악화될 수 있으니 꼭 필요한 경우에만 사용하는 것이 좋다.
리액트 훅 최적화... 너무 어려운 개념이라서 강의를 두 번씩 들었다. 두 번째 들으니 첫 번째보다 좀 이해가 갔다. 몇 번 더 들으면 완전히 이해할 수 있을까?
오늘 과제 발제가 있었는데, 과제 내용만 봐도 너무 어려워 보였다. 시작조차 할 엄두가 안나는데, 과연 제출할 수 있을까? 우선 주말동안 리액트 숙련 강의를 모두 들어야겠다.