📌 useCallback()
: 컴포넌트가 리렌더링될 때, 함수가 불필요하게 다시 생성되는 것을 방지하기 위해 사용
- 함수의 메모이제이션(기억화)을 위해 사용
- 하위 컴포넌트에 전달하는 속성 타입이 함수인 경우 캐싱(caching)할 때 사용
- 리렌더링 시 함수 참조를 유지하고 싶을 때, 특히 자식 컴포넌트에 콜백 함수를 전달할 때 유용하다.
- 해당 함수의 종속성(dependency) 배열에 지정된 값들이 변경되지 않는 한, 리-렌더링 간에 동일한 함수임을 보장하여 동일한 함수 인스턴스를 재사용할 수 있다.
- 불필요한 함수 재생성을 방지하여, 성능 최적화에 도움이 된다.
- 메모이제이션 된 함수가 많아지면, 관리 비용이 오히려 커질 수 있기 때문에 반드시 필요할 때만 사용하는 것이 좋다.
⏩ useCallback 사용 이유
- 함수 재생성 방지
- : 컴포넌트가 리렌더링될 때마다 함수가 다시 생성되는데, 이 함수들이 불필요하게 자주 생성되면 성능에 영향을 미칠 수 있다. (하위 컴포넌트에 props로 전달되거나, 의존성 배열에 포함되는 경우 ➡︎ 문제 유발)
- 참조 동일성 유지
- : 함수가 props로 하위 컴포넌트에 전달될 때, 리렌더링 시마다 함수가 새로 생성되면 하위 컴포넌트 입장에서는 해당 함수가 변경된 것으로 인식 ➡︎ 하위 컴포넌트가 불필요하게 리렌더링될 수 있다.
- useCallback을 사용하면 동일한 참조를 유지하여 이러한 불필요한 리렌더링을 방지할 수 있다.
- 성능 최적화
- : 대규모 애플리케이션이나 렌더링이 빈번하게 발생하는 경우, useCallback은 컴포넌트의 렌더링 성능을 최적화하는 데 도움을 준다.
⏩ useCallback 사용 상황
- 콜백 함수가 리렌더링에 의해 자주 재생성되는 경우
: 컴포넌트가 자주 리렌더링되거나 함수가 자식 컴포넌트에 전달되는 경우,
useCallback을 사용하여 함수 참조를 유지하는 것이 좋다. - 하위(자식) 컴포넌트에 콜백 함수를 전달할 때
: 하위 컴포넌트에 콜백 함수가 props로 전달될 때, 부모 컴포넌트가 리렌더링되면 해당 함수가 새로 생성된다.
- useCallback을 사용하면 부모 컴포넌트가 리렌더링되더라도 함수가 항상 새로 만들어지지 않고, 기존에 만들어둔 함수를 계속 사용할 수 있다.
➡︎ 리렌더링을 하지 않게 되어 불필요한 작업을 줄일 수 있다.
- useCallback을 사용하면 부모 컴포넌트가 리렌더링되더라도 함수가 항상 새로 만들어지지 않고, 기존에 만들어둔 함수를 계속 사용할 수 있다.
- useEffect, useMemo 등의 의존성 배열에 포함될 때
: 함수가 다른 훅(useEffect, useMemo 등)의 의존성 배열에 포함될 때, 해당 함수가 매번 새로 생성되면 의존성 배열이 변경된 것으로 인식되어 의도하지 않은 재실행이 발생할 수 있다.
useCallback을 사용하면, 동일한 함수 참조를 유지하여 의도한 대로 동작하게 할 수 있다. - 특정 조건에 따라 함수 생성이 비싼 경우
: 함수가 복잡한 로직을 포함하거나, 생성 비용이 비싼 경우(외부 API 호출 등), 해당 함수를 반복적으로 생성하는 것은 비효율적이기 때문에 useCallback을 사용하여 필요할 때만 함수를 생성하도록 할 수 있다.
const memoizedCallback = useCallback(
() => {
콜백 함수
},
[종속성 배열]
);
- 매개변수
- 콜백 함수
: 메모이제이션 할 함수
- 메모이제이션 된 결과를 반환한다.
- React는 첫 렌더링에서 이 함수를 반환합니다. (호출 X)
- 다음 렌더링에서 dependencies 값이 이전과 같다면, React는 같은 함수를 다시 반환한다.
- 반대로 dependencies 값이 변경되었다면, 이번 렌더링에서 전달한 함수를 반환하고 나중에 재사용할 수 있도록 이를 저장한다.
- 종속성 배열 (dependencies)
: 콜백 함수 내에서 참조되는 종속성 배열
- 이 배열에 있는 값들에 변화가 있을 때만 콜백 함수가 다시 생성된다.
- 빈 배열 []을 전달하면, 이 콜백 함수는 컴포넌트의 전체 라이프사이클 동안 한 번만 생성된다.
- 콜백 함수
예제) useCallback 미사용 vs useCallback 사용
1. useCallback을 사용하지 않은 경우❌
- handleClick 함수는 ParentComponent가 리렌더링될 때마다 새로 생성된다.
➡︎ 이 새로 생성된 함수가 ChildComponent에 전달되기 때문에 ChildComponent도 불필요하게 리렌더링 된다. - ChildComponent가 매번 리렌더링 되면서 콘솔에 "ChildComponent rendered" 메시지가 반복적으로 출력된다.
import React, { useState } from 'react';
// 하위 컴포넌트
function ChildComponent({ onClick }) {
console.log('ChildComponent rendered');
return <button onClick={onClick}>Increment</button>;
}
// 상위 컴포넌트
function ParentComponent() {
const [count, setCount] = useState(0);
// useCallback을 사용하지 않고 직접 함수 정의
const handleClick = () => {
setCount(count + 1);
};
console.log('ParentComponent rendered');
return (
<div>
<p>Count: {count}</p>
<ChildComponent onClick={handleClick} />
</div>
);
}
export default ParentComponent;
2. useCallback을 사용한 경우⭕
- handleClick 함수는 useCallback을 사용해 메모이제이션 된다.
➡︎ ParentComponent가 리렌더링되더라도 함수가 새로 생성되지 않고, 기존의 함수를 계속 사용한다. - ChildComponent는 onClick으로 전달된 함수의 참조가 바뀌지 않았기 때문에, 리렌더링되지 않는다.
➡︎ 콘솔에 "ChildComponent rendered" 메시지가 반복적으로 출력되지 않는다.
import React, { useState, useCallback } from 'react';
// 하위 컴포넌트
function ChildComponent({ onClick }) {
console.log('ChildComponent rendered');
return <button onClick={onClick}>Increment</button>;
}
// 상위 컴포넌트
function ParentComponent() {
const [count, setCount] = useState(0);
// useCallback을 사용하여 함수 참조를 유지
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
console.log('ParentComponent rendered');
return (
<div>
<p>Count: {count}</p>
<ChildComponent onClick={handleClick} />
</div>
);
}
export default ParentComponent;