From Classes to Hooks
lifecycle 메서드와 훅 일치시키기
constructor
함수형 컴포넌트는 생성자가 필요 없다. useState에서 state를 초기화할 수 있다. 만약 초기 state 계산이 비싸다면, useState에 전달해라.
SCU 구현하기
const Button = React.memo(props => {
// component
})일
prop에 shallow compare를 하기 위해 React.memo에 함수형 컴포넌트를 전달하자.
이것은 Hook이 하는 것처럼 compose하지 않기 때문에 Hook이 아니다. React.memo는 PureComponent와 동일하다. prop만 비교한다. (2번째 인자에 custom 비교 함수 전달할 수 있다. true를 리턴하면 update를 스킵한다.)
리액트 메모는 state를 비교하지 않는다. 왜냐하면 비교할 state 객체가 없기 때문이다. 하지만 children을 pure하게 만들 수 있거나 useMemo를 통해 개별 children을 최적화 할 수 있다.
연산 메모이징 하기
useMemo hook은 여러번의 렌더동안 이전 계산을 "기억해서" 연산을 캐싱하도록 해준다.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
이 코드는 computeExpensiveValue(a,b)를 호출한다. 디펜던시인 [a,b]
가 마지막 값 이후로 변경되지 않는다면, useMemo는 함수 호출을 스킵한다. 그리고 마지막에 연산된 값을 재사용한다.
렌더링 동안 useMemo에 전달된 함수는 호출되는 점을 기억해라. 렌더링 하는 동안 평범하게 할 수 없는 어떤 것이든 useMemo 내부에 전달된 함수에서 하지 마라. 예를 들어, side effect는 useMemo가 아닌 useEffect에서 해야한다.
useMemo는 시맨틱 보장이 아닌 퍼포먼스 최적화에 사용된다. 미래에 리액트는 이전에 메모된 값을 "잊어버리고" 다음 렌더 때 재계산 하도록 선택할 수 있다. 예를 들어서 offscreen component의 메모리를 확보한다. useMemo없이 너의 코드는 동작할거다. 그리고 퍼포먼스 최적화를 위해 추가될거다.
useMemo는 값비싼 child의 re-render를 스킵하도록 해준다.
function Parent({ a, b }) {
// Only re-rendered if `a` changes:
const child1 = useMemo(() => <Child1 a={a} />, [a]);
// Only re-rendered if `b` changes:
const child2 = useMemo(() => <Child2 b={b} />, [b]);
return (
<>
{child1}
{child2}
</>
);
}
이 접근은 loop에서 동작하지 않는다. 왜냐하면 hook은 loop 내부에서 동작할 수 없기 때문이다. 하지만 list item을 위해 별개의 컴포넌트로 추출하여 useMemo를 호출 할 수 있다.
값비싼 객체를 레이지하게 생성하기
useMemo는 동일한 dependencies라면 값비싼 계산을 메모이징한다. 그러나, 그것은 hint만 제공하고 재실행되지 않는 계산을 보장하지 못한다.
하지만 가끔 객체가 단 한번만 생성된다는 것을 아는 경우엔 어케 할까?
첫번째 일반적인 유즈 케이스는 initial state 생성되는게 값비쌀 때
function Table(props) {
// ⚠️ createRows() is called on every render
const [rows, setRows] = useState(createRows(props.count));
// ...
}
무시된 initial state를 다시 만들지 않기 위해 useState에 함수를 전달할 수 있다. useState initialValue를 함수로 전달시 호출 횟수
function Table(props) {
// ✅ createRows() is only called once
const [rows, setRows] = useState(() => createRows(props.count));
// ...
}
리액트는 첫 렌더에만 이 함수를 호출한다.
useRef() initial value를 재생성하고 싶지 않은 경우 한 번만 생성된 클래스 인스턴스를 원한다고 해보자.
function Image(props) {
// ⚠️ IntersectionObserver is created on every render
const ref = useRef(new IntersectionObserver(onIntersect));
// ...
}
useRef는 useState와 같이 오버로드된 특별한 함수가 아니다. 대신에, 생성과 세팅을 레이지하게 할 수 있는 함수를 작성할 수 있다.
function Image(props) {
const ref = useRef(null);
// ✅ IntersectionObserver is created lazily once
function getObserver() {
if (ref.current === null) {
ref.current = new IntersectionObserver(onIntersect);
}
return ref.current;
}
// When you need it, call getObserver()
// ...
}
Q&A
Q. SCU를 hook에서 동일하게 구현할 수 있나? A. React.memo로 비슷하게 구현할 수 있다. 비슷한 이유는 React.memo는 state를 비교하지 않는다. React.memo는 SCU보다 PureComponent와 동일하다고 볼 수 있다. 또한, useMemo를 이용하여 children component를 SCU처럼 구현할 수 있다. useMemo의 dependency를 개발자가 직접 정하여, 해당 dependency의 shallow compare시 변경되었을 때만 리렌더링한다.
Q. 연산이 비싼 객체가 단 한번 생성되는 경우, 레이지하게 생성하려면 어떤 기법을 사용할 수 있는가? A. 특정 변수에 useRef를 호출. 함수 하나를 만들어서 ref.current에 값을 할당하는 코드 작성. 필요할 때 함수 호출.
놓치기 쉬운 최적화
useState(createObjByExpensiveCalculation());
위 코드는 함수가 리렌더 될 때마다 호출된다. JS 함수 호출 시 인자를 먼저 평가하기 때문이다.
다음과 같이 고쳐주자
useState(() => createObjByExpensiveCalculation());
Last updated
Was this helpful?