함수를 리액트의 내부 저장 공간에 저장해서 함수 객체가 실행될 때마다 이를 재사용할 수 있게 합니다.
const cachedFn = useCallback(fn, dependencies)
저장하려는 함수입니다.
useCallback은 저장된 함수를 반환해줍니다.
함수가 의존하는 값을 갖는 의존성 배열입니다.
의존 값들이 바뀌면 저장된 함수를 다시 연산하라고 알려줍니다.
빈 배열이라면 저장된 함수를 초기 한 번만 연산합니다.
아래 함수에서 버튼을 클릭할 때마다 state가 true와 false로 바뀌는 함수를 실행합니다.
// App.js
import React, { useState, useCallback } from "react";
import Button from "./components/UI/Button/Button";
function App() {
const [state, setState] = useState(false);
const toggleStateHandler = () => {
setState((prevState) => !prevState);
};
return (
<div className="app">
<h1>Hi there!</h1>
<Button onClick={toggleStateHandler}>Change State</Button>
</div>
);
}
export default App;
App 컴포넌트의 버튼을 클릭해도 Button 컴포넌트의 Props는 바뀌지 않기 때문에 불필요한 재평가를 막기 위해 React.memo(Button)를 사용해서 재평가를 막았습니다.
// Button.js
import React from "react";
const Button = (props) => {
return (
<button
type={props.type || "button"}
className={`${classes.button} ${props.className}`}
onClick={props.onClick}
disabled={props.disabled}
>
{props.children}
</button>
);
};
export default React.memo(Button);
하지만 버튼을 클릭했을 때 Button 컴포넌트는 재평가됩니다.
App 컴포넌트의 버튼을 클릭하면 toggleStateHandler가 실행되어 state가 바뀌며 App 컴포넌트가 재평가 됩니다.
재평가 될때 toggleStateHandler함수는 참조타입이기 때문에 모양은 같지만 메모리 새로운 곳에 생성되며 새로운 값으로 인식됩니다.
따라서 onClick Props가 바뀐것이 되어 React.memo(Button)로 재평가를 막지 못하게 됩니다.
이 부분이 이해가 되지 않으신다면 '데이터 타입과 복사, 복제'에서 '같은 참조 값을 가진 두 변수 비교' 를 확인해보시길 바랍니다.
useCallback을 사용하여 첫 평가에 toggleParagraphHandler를 메모리에 저장하고 재평가가 일어나면 저장한 함수를 반환하며 Button 컴포넌트의 재평가를 막습니다.
(메모리에 저장한 함수는 같은 값이기 때문에 props가 변하지 않았으므로)
import React, { useState, useCallback } from "react";
import Button from "./components/UI/Button/Button";
function App() {
const [state, setState] = useState(false);
// useCallback
const toggleStateHandler = useCallback(() => {
setState((prevState) => !prevState);
}, []);
return (
<div className="app">
<h1>Hi there!</h1>
<Button onClick={toggleStateHandler}>Change State</Button>
</div>
);
}
export default App;
위의 함수에서 allowToggle state가 추가되었습니다.
Change State 버튼을 클릭할 수 있는 조건을 추가했습니다.
조건은 Allow Toggling 버튼으로 allowToggle state를 true로 바꿔야하도록 코드를 짜고 싶습니다.
allowToggle state를 true로 바꿔야 toggleStateHandler로 state를 바꿀 수 있습니다.
import React, { useState, useCallback } from "react";
import Button from "./components/UI/Button/Button";
function App() {
const [state, setState] = useState(false);
const [allowToggle, setAllowToggle] = useState(false);
const toggleStateHandler = useCallback(() => {
if (allowToggle) {
setState((prevState) => !prevState);
}
}, []);
const allowToggleHandler = () => {
setAllowToggle(true);
};
return (
<div className="app">
<h1>Hi there!</h1>
<Button onClick={allowToggleHandler}>Allow Toggling</Button>
<Button onClick={toggleStateHandler}>Change State</Button>
</div>
);
}
export default App;
하지만 위와 같이 코드를 작성하면 allowToggle이 true가 되어도 state를 true로 바꿀 수 없습니다.
이유는 allowToggleHandler로 allowToggle의 값을 바꿔도 useCallback()에 저장된 함수에 있는 allowToggle의 값은 바뀌지 않기 때문입니다.
useCallback은 처음 App컴포넌트가 랜더링 될때 전달된 함수를 저장해놓습니다.
따라서 저장된 toggleStateHandler함수 내부에 있는 allowToggle도 false인 채로 바뀌지 않는 것입니다.
useCallback의 두번째 인자인 의존성 배열에 alowToggle을 추가하면 해결됩니다.
alowToggle이 바뀔 때마다 저장된 함수를 다시 연산하고, alowToggle은 최신 값을 유지하게 됩니다.
함수가 다시 연산되었을 때 alowToggle이 true로 저장되었다면 Change State버튼을 통해 state를 바꿀 수 있게 됩니다.
import React, { useState, useCallback } from "react";
import Button from "./components/UI/Button/Button";
function App() {
const [state, setState] = useState(false);
const [allowToggle, setAllowToggle] = useState(false);
const toggleStateHandler = useCallback(() => {
if (allowToggle) {
setState((prevState) => !prevState);
}
}, [allowToggle]);
const allowToggleHandler = () => {
setAllowToggle(true);
};
return (
<div className="app">
<h1>Hi there!</h1>
<Button onClick={allowToggleHandler}>Allow Toggling</Button>
<Button onClick={toggleStateHandler}>Change State</Button>
</div>
);
}
export default App;