-
Notifications
You must be signed in to change notification settings - Fork 2
Description
React API 레퍼런스: https://ko.react.dev/reference/react/useCallback
useCallback 함수란?
→ React에서 제공하는 Hook으로, 주로 렌더링 성능을 최적화해야 하는 상황에서 사용한다. 함수를 재사용 가능하게 해 불필요한 함수가 새로 생성되는 것을 방지해준다.
import { useCallback } from 'react';
...
const cachedFn = useCallback(fn, dependencies);useCallback 함수는 2개의 매개변수를 가진다.
첫번째 파라미터 (fn) : 생성하려는 함수.
두번재 파라미터 (dependencies) : 함수에서 참조되는 값들의 배열.
배열안의 값들 중 하나라도 바뀌면 함수가 재생성됨.
아래 예시를 보자. count의 값을 1씩 증가시키는 함수이다.
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(count + 1);
}, [count]);increment 함수가 호출될 때 setCount를 통해 count의 상태가 변화한다.
→ 의존성 배열 안의 값인 count가 변화했으므로 increment 함수가 재생성된다.
→ increment 함수는 올바르게 최신 상태 값(count)를 반영할 수 있다.
만약 count를 의존성 배열에 포함하지 않는다면 어떻게 될까?
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(count + 1);
}, []); // 빈 배열increment 함수는 초기 렌더링 시의 count값(0)을 참조하게 된다.
→ count의 값이 변화하더라도 함수 내부에서는 그 변화를 알 수 없다.
→ 최신 count 값을 사용할 수 없어 초기 렌더링 시의 count 값인 0만을 참조하게 된다.
두번째 파라미터의 배열은 함수의 재생성 여부를 결정하는 중요한 요소이다.
※ 별개로 위 처럼 두 번째 파라미터에 빈 배열을 사용한다면 최초 랜더링될 때 생성된 함수만을 계속해서 재사용한다.
useCallback 함수가 유용한 상황
어떨 때 useCallback함수를 사용하면 좋을까? 아래 예제 코드를 살펴보자.
count의 값을 증가하거나 text의 값을 변경하는 코드이고, 이번엔 자식과 부모 컴포넌트로 이루어져 있다.
import React, { useState, useCallback } from 'react';
// 자식 컴포넌트
const ChildComponent = React.memo(({ onClick }) => {
return (
<button onClick={onClick}>Increment</button>
);
});
// 부모 컴포넌트
function ParentComponent() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
// useCallback을 사용 안할 경우
const increment = () => {
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<p>Text: {text}</p>
<p>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
/>
</p>
<ChildComponent onClick={increment} />
</div>
);
}
export default ParentComponent;부모 컴포넌트는 count와 text 두 가지의 상태를 관리한다.
자식 컴포넌트에 increment 라는 props를 전달한다.
자식 컴포넌트는 React.memo로 래핑되어 있어, 부모로부터 받은 props가 변경되지 않는 한 리렌더링되지 않는다.
만약 count와 관련없는 text가 변경되면 어떻게 될까?
onChange 함수를 통해 text가 변경된다.
→ 부모 컴포넌트가 리렌더링되면서 increment 함수가 재생성된다.
→ increment라는 props가 변화했으므로, 자식 컴포넌트가 불필요하게 리렌더링된다.
→ React.memo를 통해 자식 컴포넌트를 최적화한 의미가 없어지고 성능 저하로 이어진다.
만약 increment를 useCallback 함수를 이용해 선언한다면?
import React, { useState, useCallback } from 'react';
...
// 부모 컴포넌트
function ParentComponent() {
...
// useCallback을 사용할 경우
const increment = useCallback(() => {
setCount(count + 1);
}, [count]);
...
}
export default ParentComponent;text의 값이 변경되어 부모 컴포넌트가 리렌더링된다.
→ count의 값은 바뀌지 않았으므로, increment 함수는 재생성되지 않고, 동일한 참조가 유지되어 자식 컴포넌트는 변경되지 않은 props가 전달된다.
→ React.memo로 인해 자식 컴포넌트는 리렌더링되지 않는다.
→ 성능 최적화
모든 함수를 useCallback으로 선언하면 좋지 않은가?
이쯤에서 필자는 하나의 의문이 들었다.
모든 함수를 useCallback으로 선언하면 언제든지 불필요한 함수가 재생성되는 것을 방지할 수 있지 않을까?
하지만 그건 좋은 방법이 아니라고 한다.
오버헤드 증가
useCallback 함수는 메모제이션을 위해 메모리와 cpu자원을 사용한다.
무분별한 사용은 메모제이션으로 인한 오버헤드가 증가할 수 있다.
코드 복잡성 증가
useCallback 함수는 어떤 성능을 최적화하기 위해 사용되었는지 명시되어야 한다. 그렇지 않으면 협업 환경에서 코드의 가독성이 떨어지고 혼란을 일으켜 유지보수가 어려워질 수 있다.