부모 컴포넌트가 바뀌면 자식 컴포넌트도 리렌더링이 일어난다. 리액트에서 리렌더링이 자주 일어난다는 것은 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를 남발하면 메모리를 너무 많이 확보하기 때문에 오히려 성능이 악화될 수 있으니 꼭 필요한 경우에만 사용하는 것이 좋다.
리액트 훅 최적화... 너무 어려운 개념이라서 강의를 두 번씩 들었다. 두 번째 들으니 첫 번째보다 좀 이해가 갔다. 몇 번 더 들으면 완전히 이해할 수 있을까?
오늘 과제 발제가 있었는데, 과제 내용만 봐도 너무 어려워 보였다. 시작조차 할 엄두가 안나는데, 과연 제출할 수 있을까? 우선 주말동안 리액트 숙련 강의를 모두 들어야겠다.