Hook이란?
React 공식문서에서는 다음과 같이 규정하고 있다.
"Hook은 React 16.8에 새로 추가된 기능입니다. Hook은 class를 작성하지 않고도 state와 다른 React의 기능들을 사용할 수 있게 해줍니다."
즉 함수형 컴포넌트에서 상태 값 및 다른 여러 기능을 사용하기 편리하게 해주는 메소드를 의미한다. Hook은 class가 아닌 function으로만 React를 사용할 수 있기 때문에 클래스형 컴포넌트에서는 동작하지 않는다.
1. 리액트 함수의 최상위에서만 호출해야 한다.
반복분이나 조건문,중첩된 함수 내에서 Hook을 실행하면 예상한 대로 동작하지 않을 우려가 있다. useState나 useEffect 같은 Hook들이 여러 번 사용될 수 있는데, React는 이 Hook을 호출되는 순서대로 저장을 해놓게 된다.
그런데 조건문, 반복문 안에서 Hook을 호출하게 되면 호출되는 순서대로 저장을 하기 어려워지고, 결국 예기치 못한 버그를 초래하게 될 수 있다.
2. 오직 리액트 함수 내에서만 사용되어야 한다.
리액트 함수형 컴포넌트나 커스텀 Hook이 아닌 다른 일반 JavaScript 함수 안에서 호출해서는 안 된다는 의미이다.
useMemo & useCallback
React Hook은 함수 컴포넌트가 상태를 조작하고 및 최적화 기능을 사용할 수 있게끔 하는 메소드이다. 그 중 렌더링 최적화를 위한 Hook도 존재하는데, useCallback과 useMemo가 바로 그 역할을 한다.
useMemo는 특정 값(value)를 재사용하고자 할 때 사용하는 Hook이다.
/* useMemo를 사용하기 전에는 꼭 import해서 불러와야 한다. */
import { useMemo } from "react";
function Calculator({value}){
const result = useMemo(() => calculate(value), [value]);
return <>
<div>
{result}
</div>
</>;
}
value 는 일종의 값으로서 렌더링을 할 때마다 이 value값이 계속 바뀌는 게 아니라고 가정해보면, 이 값을 어딘가에 저장을 해뒀다가 다시 꺼내서 쓸 수만 있다면 굳이 calculate 함수를 호출할 필요도 없을 것이다. 이때 사용하는 것이 바로 useMemo이다.
위 예시와 같이 useMemo를 호출하여 calculate를 감싸주면, value값이 동일할 경우에는 이전 렌더링의 value값을 그대로 재활용할 수 있게 된다.
useCallback
useMemo는 값의 재사용을 위해 사용하는 Hook이라면, useCallback은 함수의 재사용을 위해 사용하는 Hook이다.
/* useCallback을 사용하기 전에는 꼭 import해서 불러와야 한다. */
import React, { useCallback } from "react";
function Calculator({x, y}){
const add = useCallback(() => x + y, [x, y]);
return <>
<div>
{add()}
</div>
</>;
}
해당 컴포넌트가 리렌더링 되더라도 그 함수가 의존하고 있는 값인 x와 y가 바뀌지 않는다고 가정할 때, 함수 또한 메모리 어딘가에 저장해 뒀다가 다시 꺼내서 쓸 수 있을 것이다.
이때 useCallback Hook을 사용하면 x와 y값이 동일하다면 다음 렌더링 때 이 함수를 다시 사용하게 된다. useCallback을 사용하는 것은 큰 의미가 없거나 오히려 손해인 경우도 있다.
그러면 언제 사용하는 게 좋을까?
자식 컴포넌트의 props로 함수를 전달해줄 때 이 useCallback을 사용하기가 좋다.
useCallback과 참조 동등성
useCallback은 참조 동등성에 의존한다. React는 기본적으로 JavaScript의 문법을 따라가고, JavaScript에서 함수는 객체이다. 객체는 메모리에 저장할 때 값을 저장하는 게 아니라 값의 주소를 저장하기 때문에, 반환하는 값이 같을 지라도 일치연산자로 비교했을 때 false가 출력된다.
function doubleFactory(){
return (a) => 2 * a;
}
const double1 = doubleFactory();
const double2 = doubleFactory();
double1(8); // 16
double2(8); // 16
double1 === double2; // fals
double1과 double2는 같은 함수를 할당했음에도 메모리 주소 값이 다르기 때문에 같다고 보지 않는다. 두개의 함수는 동일한 코드를 공유하더라도 메모리 주소가 다르기 때문에, 메모리 주소에 의한 참조 비교 시 다른 함수로 본다.
그러나 useCallback을 이용해 다시 사용하면 함수의 메모리 주소 값을 저장했다가 다시 사용한다는 것과 같다고 볼 수 있다. 따라서 React 컴포넌트 함수 내에서 다른 함수의 인자로 넘기거나 자식 컴포넌트의 prop으로 넘길 때 예상치 못한 성능 문제를 막을 수 있다.