useCallback
이란 함수를 메모이제이션을 하기 위해 사용되는 hook
입니다.useCallback
을 사용하게 되면 해당 컴포넌트가 리랜더링되더라도 그 함수가 의존하는 값이 변경되지 않았다면 함수를 재생성하지 않고 기존 함수를 그대로 반환합니다.const x = 3;
const y = 7;
const add = useCallback(() => x + y, [x, y]);
개인적으로 평소 프로젝트를 진행하면서 가장 많이 사용하고 있는 hook
이라고 생각합니다.
함수형 컴포넌트 내부에서 handler 또는 특정 함수를 생성할 때 useCallback
을 사용하면 사용하지 않는 것보다는 성능 이점을 가저올 수 있겠지라고 생각했습니다. ( 손해는 안보겠지?? )
하지만 useCallback
에 대한 비용과 혜택에 관련된 글을 보면서 해당 hook
에 대한 생각이 변화게 되었습니다.
function App() {
const initStore = ["book", "phone", "computer"];
const [products, setProducts] = useState(initStore);
const buyProduct = (product) => {
setProducts((products) => products.filter((item) => product !== item));
};
return (
<div>
<h1>WooDong Shop</h1>
<div>
{products.map((product, index) => (
<div key={index}>
<button onClick={() => buyProduct(product)}>buy</button>
<span>{product}</span>
</div>
))}
</div>
</div>
);
}
export default App;
buyProduct
함수에 useCallback
을 적용한다면 과연 성능이 개선된다고 볼 수 있을까?// useCallback 미사용
const buyProduct = (product) => {
setProducts((products) => products.filter((item) => product !== item));
};
// useCallback 사용
const buyProduct = useCallback((product) => {
setProducts((products) => products.filter((item) => product !== item));
}, []);
정답은 오히려 useCallback
을 사용하지 않는 함수가 성능이 더 높을 수 있다고 합니다!
기본적으로 프로그래밍 언어는 코드라인에 있는 코드를 실행하게 되면 비용이 수반됩니다!
useCallback
의 원래 모습을 한번 보시죠!
useCallback
의 내부적인 기능이 추가됨에 따라 더 많은 함수가 정의가 되고 결국 메모리를 더 사용하게 됩니다.const buyProduct = (product) => {
setProducts((products) => products.filter((item) => product !== item));
};
const handleUseCallBack = useCallback(buyProduct, []);
useMemo
는 어떤 타입 또는 값들을 모두 메모제이션해주는 hook
입니다.useCallback
과 매우 비슷합니다!const initStore = ["book", "phone", "computer"]
// initStore를 다시 만들기 싫을때 useMemo를 활용
const initStore = useMemo(()=>["book", "phone", "computer"], []);
useCallback
처럼 효율적인 방법이 아닙니다!useCallback
처럼 함수를 호출하면서 다른 함수들의 코드가 메모리에 할당됩니다.const initStore = ["book", "phone", "computer"]
function App(){
const [products, setProducts] = useState(initStore);
}
useCallback
/ useMemo
는 무조건 사용하는 것이 과연 좋은 것일까?사실 useCallback
/ useMemo
를 통해 최적화를 진행해도 해당 훅을 사용해서 최적화를 통해 얻어지는 효율은 거의 체감하기 힘든 것 같습니다. ( 요즘 브라우저 / 컴퓨터 사양이 높기 때문에... )
즉 성능 개선은 공짜로 이루어지는 것이 아니고 항상 그에 해당하는 비용이 더 생기게 됩니다.
성능 개선을 통해 얻어지는 효율성이 그 비용을 상쇄할 수 없다면 효율적으로 볼 수 없다는 의미입니다!
useCallback
/ useMemo
을 언제 사용하는게 좋을까?useMemo
/ useCallback
이 만들어진 배경이 중요합니다!! ( 크게 2가지 )
value
이지만 참조하는 값이 다르기 때문에 다른 value
로 인식합니다!// 원시 타입
1 === 1 // true
'a' === 'a' // true
// 참조 타입
{} === {} // false
[] === [] // false
useEffect
는 컴포넌트가 렌더링 되면 items
의 참조 동일성을 계속 체크합니다.itmes
라는 객체가 매번 새로 만들어지기 때문에 렌더링 될때마다 계속 호출되게 됩니다.items
가 변경될 때 useEffect
의 콜백 함수를 실행하고 싶었지만 결국 아무 의미없이 컴포넌트가 리랜더링 될 때마다 실행됩니다!function Item({ book, pencil }) {
const items = { book, pencil };
useEffect(() => {
handleItems(options);
}, [items]);
return <div>Item</div>;
}
function Bag() {
return <Item book="book" pencil="pencil" />;
}
useEffect
의 dependency에 props
로 전달받은 book
/ pencil
를 직접 할당하고 useEffect
내부에서 items
를 생성하면 됩니다!function Item({ book, pencil }) {
useEffect(() => {
const items = { book, pencil };
handleItems(options);
}, [book, pencil]);
return <div>Item</div>;
}
function Bag() {
return <Item book="book" pencil="pencil" />;
}
function Bag() {
// 참조 타입
const book = () => { return 'book' };
const pencil = ['red', 'blue', 'green'];
return <Item book={book} pencil={pencil} />;
}
useCallback
과 useMemo
입니다!function Item({ book, pencil }) {
useEffect(() => {
const items = { book, pencil };
handleItems(options);
}, [book, pencil]);
return <div>Item</div>;
}
function Bag() {
// useCallback / useMemo를 적용
const book = useCallback(() => { return 'book' }, []);
const pencil = useMemo(() => ['red', 'blue', 'green'], []);
return <Item book={book} pencil={pencil} />;
}
useMemo
가 만들어진 또 다른 이유이기도 합니다.useMemo
를 사용해 연속으로 같은 값을 다시 계산하지 않도록 하여 속도를 더 향상시킬 수 있습니다.animation
이라는 복잡한 계산을 진행하는 함수가 존재할 경우 start
/ end
값이 변화지 않으면 그대로 이미 계산된 값을 사용하도록 할 수 있습니다!const animation = (start, end) => {...};
function App({start, end}) {
const value = useMemo(() => animation(start, end), [start, end]);
...
}
useCallback
과 useMemo
를 성능을 향상시킨다는 초점에 맞춰서 무분별하게 사용한다면??dependencies
배열에 들어가는 참조 value
들이 누락되거나 잘못 넣게되면서 휴먼 에러가 자주 발생합니다.dependencies
배열 내부의 값들이 메모제이션되면서 가비지 컬랙터가 안되게 만들 수 있습니다.useCallback
과 useMemo
를 사용하여 성능 개선을 통해 얻어지는 이점이 추가된 비용을 상쇄할 수 있다면 의미있게 사용할 수 있지 않을까? 생각합니다!