필자는 재사용하고 싶을 때 사용한다는 말, 성능 최적화 등등의 말만 보고 대부분 함수에다가 useCallback을 남용하였습니다. 이로 인하여 코드가 복잡해지거나, 유지보수에 어려운 점을 겪는 경험을 하여 ..useCallback을 이렇게 남용해도 되는 건가냐는 생각 하게 되어, 여러 문서를 보고 정리해 보았습니다 !
제대로 알고 사용하자 ! 모르고 사용하면 중범죄다!
반성 후 열심히 코드 고쳤습니다.
useCallback 특정 함수를 새로 만들지 않고 재사용하고 싶을 때 사용합니다. 메모제이션 콜백을 반환합니다.
즉 리 렌더링 최소화하기 위해 사용합니다.
한번 만든 함수는 필요할 때만 만들어지고 재사용 !
Component에서 props 가 바뀌지 않으면 렌더링 하지 않습니다.
1. 함수 동등성 이해
javascript 의 타입의 특징인 primitive type 과 non-primitive type
- string, number 의 경우에는 값 자체를 저장 (primitive type)
- object, array 의 경우에는 주소값을 저장하여 reference (non-primitive type)
자바스크립트 함수는 일급 객체! 즉 함수는 객체다.
function factory() {
return (a, b) => a + b;
}
const sum1 = factory();
const sum2 = factory();
sum1(1, 2); // => 3
sum2(1, 2); // => 3
sum1 === sum2; // => false
sum1 === sum1; // => true
sum1, sum2이 담는 factory 함수는 동일한 소스 코드이지만 sum1, sum2는 서로 다른 메모리 주소를 가리키고 있기 때문에 false가 나옵니다. useCallback(callbackFun, [deps]) useEffect와 마찬가지로 dependency에 연관된 요소들을 전달하고, 이 종속성 요소 중 하나가 변경 된 경우에만 변경 된 콜백을 리턴합니다.
이를 통해 불필요한 렌더링을 방지하고, 참조 동등성에 의존하는 최적화된 하위 구성 요소에 콜백을 전달합니다.
const memoizedCallback = useCallback(
() => {doSomething(a, b);}, // inline callbck
[a, b], // dependency
);
2. useCallback- 자식컴포넌트 전달 시 잘못된 사용
import { useState, useCallback } from "react";
import Button from "./Button";
function App() {
const [count, setCount] = useState(0);
const handleCountIncrease = useCallback(() => {
setCount((count) => count + 1);
}, []);
return (
<div>
<div>{count}</div>
<Button onClick={handleCountIncrease} />
</div>
);
}
export default App;
//Button.js
import React from "react";
const Button = ({ onClick }) => {
return <button onClick={onClick}>sss</button>;
};
export default Button;
handleCountIncrease 함수에 useCallback을 사용하였다. 하지만 버튼 컴포넌트는 리 렌더링한다. count가 업데이트되어 자식 컴포넌트가 리 렌더링 되는 것이다. 이것을 해결하기 위해 React.memo를 사용하여 해결할 수 있다. 간략하게 React.memo는 컴포넌트를 메모제이션 해준다. 부모 컴포넌트에서 받은 props가 같으면 메모제이션 해둔 결과를 가져온다.
리렌더링 조건
- state 변경
- 새로운 props 들어올 때 , 즉 전달 받은props값이 없데이트가 될 때
- 부모 컴포넌트가 리 렌더링
props가 존재하지 않아고 부모컴포넌트가 리렌더링 되면 자식도 리 렌더링된다. - shouldCompentUpdate 에서 ture 반환
- forceUpdate가 실행
import { useState, useCallback } from "react";
import Button from "./Button";
function App() {
const [count, setCount] = useState(0);
const handleCountIncrease = useCallback(() => {
setCount((count) => count + 1);
}, []);
return (
<div>
<div>{count}</div>
<Button onClick={handleCountIncrease} />
</div>
);
}
export default App;
//Button.js
import React from "react";
const Button = ({ onClick }) => {
return <button onClick={onClick}>sss</button>;
};
// export default Button;
export default React.memo(Button);
import React, { useMemo, useCallback } from "react";
const ChildComponent = ({ myFunc, myValue }) => {
return <div onClick={myFunc}>{myValue}</div>;
};
const FatherComponent = ({ number1, number2 }) => {
const memoizedFunc = useCallback(() => {
console.log(`numbers: ${number1}, ${number2}`);
}, [number1, number2]);
const memoizedSum = useMemo(() => number1 + number2, [number1, number2]);
return <ChildComponent myFunc={memoizedFunc} myValue={memoizedSum} />;
3. useCallback 사용하면 잘못된 걸까?
위에 예제만 보았을 때만 생각 했을 때는 useCallback이 최적화에는 만능인줄 알았다 ..ㅠ
// case 1
const handleCountIncrement = useCallback(() => {
setCount(count + 1);
}, []);
// case 2
const handleCountIncrement = () => {
setCount(count + 1);
};
return <Button onClick={handleCountIncrement} />;
인라인 함수는 useCallback을 사용하는 것이 성능에 좋다고 생각했다. 모든 추상화 및 최적화 코드에는 비용이 든다. useCallback을 사용하기 위해선 전후 성능을 비교 후에 사용하자.
3-1. 이럴땐 안쓰는 게 낫다 ~!
함수가 맡는 롤이 작은 경우는 useCallback 사용하는게 메모리를 더 잡아먹고 괜한 메모리낭비이므로 사용하지 말도록 하자.
- setState나 dispatch를 단순 호출
- 일반 함수의 경우, toggle이나 incrememt 같은 단순한 연산
출처: https://mingeesuh.tistory.com/entry/React-성능-최적화를-위한-useMemo-와-useCallback [코딩마차]
4. useCallback을 언제 사용해야 하나요?
메모이제이션이 작동하도록 하려면 React가 이 데이터를 처리하고 계산하고 저장해야 합니다(가비지 수집기 처리 등). 내 의견은 이러한 후크는 다음과 같은 경우에만 사용해야 한다는 것입니다.
- 참조 동일성의 이점을 고려
- 자식 컴포넌트에 함수를 props로 넘겨주는데 , 해당 자식 컴포넌트에 넘겨주는 함수 때문에 불필요한 리렌더링이 일어난다고 판단될 경우
- 과도한 계산이 포함된 함수
- 함수 자체가 매우 복잡하거나, 비용이 많이 드는 경우
import { useCallback } from 'react';
export function MyParent({ term }) {
const onItemClick = useCallback(event => {
console.log('You clicked ', event.currentTarget);
}, [term]);
return (
<MyBigList
term={term}
onItemClick={onItemClick}
/>
);
}
import useSearch from './fetch-items';
function MyBigList({ term, onItemClick }) {
const items = useSearch(term);
const map = item => <div onClick={onItemClick}>{item}</div>;
return <div>{items.map(map)}</div>;
}
export default React.memo(MyBigList);
정리
- React를 남용하지 마십시오. 콜백을 사용합니다. React에 래핑된 함수. UseCallback은 가비지 수집으로 수집되지 않으며 새 참조가 생성됩니다. 한편, React에 의해 생성된 클로저. UseCallback은 콜백 함수 및 종속성에 대한 참조를 유지합니다. 따라서 남용하면 메모리 과부하가 발생할 수 있습니다. 리액트 만들기. UseCallback 자체가 성능 저하입니다.
- React의 목표. UseCallback은 컴포넌트 내부의 여러 함수 생성 문제를 해결하기 보다는 불필요한 re-rendering을 줄이기 위한 것입니다. 반응합니다. UseCallback은 함수에 대한 참조를 저장할 수 있습니다. 종속 목록이 변경되지 않으면 이 함수에 대한 참조가 변경되지 않습니다. 함수 구성 요소에 인수로 전달된 이러한 함수는 참조가 같지 않기 때문에 구성 요소를 불필요하게 렌더링하지 않습니다.
- useCallback + React.memo는 좋은 일치를 재생하고 불필요한 재 렌더링을 줄일 수 있습니다.
- React.memo도 남용되어서는 안됩니다. 구성 요소가 본질적으로 복잡하고 다시 렌더링하는 데 비용이 많이 드는 경우에만 React.memo 고려하십시오.
참조
useMemo 및 useCallback 을 사용하는 경우
'React' 카테고리의 다른 글
JSX 조건부 렌더링 : 삼항 연산자 vs && (0) | 2022.06.29 |
---|---|
Atomic Design Pattern (0) | 2022.05.13 |
Next.js 수동 설치 (0) | 2022.01.07 |
[React] Storybook 설치하기 (0) | 2021.12.28 |
React Router V6 업데이트 (0) | 2021.12.21 |