들어가기에 앞서 메모이제이션(memoization)을 알아보자.
정의를 찾아보면 "컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술"이라고 정의되어있다.
요약해보면
첫 계산을 완료한뒤 다음 수행에선 계산 과정은 생략하고 저장해놓은 계산 결과를 반환한다는것 같다.
메모이제이션 느낌을 알았으니
이제 useCallback()
을 알아보자.
useCallback()
은 함수를 메모이제이션하기 위해서 사용되는 hook 함수
첫 번째 매개변수 함수를 두 번째 매개변수인 배열 내의 값이 변경되기 전까지 저장해놓고 재사용할 수 있음.
const memozizedCallback = useCallback(함수, 배열);
자바스크립트에서 함수는 객체로 취급된다.
따라서 배열, 객체와 비슷하게 함수는 값이 아닌 참조로 비교되는데 아래 코드를 보면
const func1 = function() {
return 1;
};
const func2 = function() {
return 2;
};
console.log(func1 === func2); // false
결과가 왜 false가 나올까?
위 두 함수는 매우 비슷해보이지만 서로 다른 함수 객체이다.
즉, 두 개의 함수 리터럴은 각각 독립적인 함수 객체를 생성한다.
React에서는 이처럼 Component 내에서 함수를 정의하게 되는데
모든 단일 render에서는 매번 동일하지만 고유한 함수를 생성할 것 이다.
그럼 매번 고유한 함수가 생성되면 React에서는 해당 함수를 새로운 함수로 판단하는데
부모 컴포넌트가 자식 컴포넌트에게 이러한 새로운 함수를 props로 전달할 때마다, 자식 컴포넌트는 새로운 함수를 받은 것으로 간주하고 리렌더링된다.
함수를 포함한 JavaScript 객체는 참조값으로 비교되므로 새로운 함수가 생성될 때마다 이 함수의 참조값이 바뀌게 된다.
React 컴포넌트는 props
또는 state
가 변경되면 리렌더링된다.
이러한 동작은 성능 측면해서 고려해봐야 하는데, 불필요한 함수 생성은 불필요한 리렌더링을 초래할 수 있으므로, 함수가 자주 변경되지 않는 경우에는 최대한 함수를 재사용하도록 노력하는 것이 좋다.
이를 해결하기 위해 useCallback()
을 사용하여 불필요한 리렌더링을 막을 수 있다.
첫 번째 인자로 함수를 전달하며, 해당 함수를 메모이제이션하여 render 간에 전달한다.
두 번째 인자로는 의존성 값을 전달한다. 두 번째 인자로 넘겨준 의존성이 변경 되었을 경우만 리렌더링이 된다.
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b]
);
useCallback
함수는 두 개의 인자를 받는다.
첫번째 인자는 메모이제이션하고자 하는 함수이며, 두 번째 인자는 의존성 배열(dependency array)이다.
() => doSomething(a, b);
부분은 메모이제이션하려는 함수이며, 이 함수는 'a'와 'b'라는 변수를 사용하여 어떤 작업(doSomething)을 수행한다.
[a, b]
는 의존성 배열로, 이 배열에 나열된 변수들의 값이 변경될 경우에만 메모이제이션된 함수가 다시 생성된다. 즉, 리렌더링된다. 값이 변경되지 않는다면 이전에 메모이제이션된 함수가 재사용된다.
props로 전달해야 할 함수를 만들 때는 useCallback()
을 사용하여 함수를 감싸는 것이 좋다.
const TodoInsert = () => {
const [value, setValue] = useState('');
const onChange = useCallback((e) => {
setValue(e.target.value);
}, []);
return (
<form className="TodoInsert">
<input
placeholder="할 일을 입력하세요"
value={value}
onChange={onChange}
/>
<button type="submit">
<MdAdd/>
</button>
</form>
);
}