React.memo는 컴포넌트를 메모이제이션하였다면,
useCallback은 인자로 들어오는 함수 자체를 기억(메모이제이션) 한다.
Q . 근데 함수를 메모이제이션 할 필요가 있을 까??
A .
위의 이유로 ( 내 생각임... 틀리면 feedback 주세요 )
useCallback으로 함수 자체를 memoization
하는 것이 필요하다고 생각 된다.
import {useCallback} from "react"
useCallback(()=>{
... // 이 안에 memoization할 함수를 넣어주세요
},[])// 의존성 배열인데 함수의 상태를 새롭게 그려줄 녀석입니다.
// App.jsx
function App () {
console.log("App 컴포넌트가 렌더링되었습니다!")
const [count, setCount] = useState(0);
// 1을 증가시키는 함수
const onPlusButtonClickHandler = () => {
setCount(count + 1);
};
// count를 초기화해주는 함수
const initCount = () => {
setCount(0);
};
return (
<>
<h3>카운트 예제입니다!</h3>
<p>현재 카운트 : {count}</p>
<button onClick={onPlusButtonClickHandler}>+</button>
<div>
<Box1 initCount={initCount} />
<Box2 />
<Box3 />
</div>
</>
);
}
...
Box1.jsx
...
function Box1({ initCount }) {
console.log("Box1이 렌더링되었습니다.");
const onInitHandler = () => {
initCount();
};
return (
<div style={boxStyle}>
<button onClick={onInitHandler}>초기화</button>
</div>
);
}
export default React.memo(Box1)
+ 버튼
을 누르고 난 후 초기화
버튼을 누를 때 모두
App
컴포넌트와 Box1
컴포넌트가 리렌더링 되는 것을 보는데,
:????: 뭐지?? React.memo를 통해서 Box1.jsx는 메모이제이션을 했는데....
이유는 App.jsx가 Box1.jsx에서 초기화 버튼을 누름으로서 리렌더링이 시작되고
props로 전달 받은
function Box1({ initCount }) {
const onInitHandler = () => {
initCount();
};
.
.
...
}
onInitHandler
함수 코드가 다시 만들어지기 때문이다.
자바스크립트에서는 함수도 객체의 한 종류이다.
따라서 모양은 같더라도 다시 만들어지면 그 주솟값이 달라지고 이에 따라 하위 컴포넌트인 Box1.jsx
는 props가 변경됐다
고 인식하여서 Box1
컴포넌트가 리렌더링 일어난다.
// App.jsx에서
// 변경 전
const initCount = () => {
setCount(0)
}
// 변경 후
const initCount = useCallback( () => {
setCount(0)
},[])
이렇게 하면 Box1.jsx 컴포넌트는 리렌더링이 일어나지 않게 된다.
count를 쵝화 할 때, 콘솔 을 찍어보면
// count를 초기화해주는 함수
const initCount = useCallback(() => {
console.log(`[COUNT 변경] ${count}에서 0으로 변경되었습니다.`);
setCount(0);
}, []);
...
현재 count가 7일 때 [초기화] 버튼을 누를 꺼니까 콘솔에는 7에서 0으로 가 보여야하는 것이 내 예상이였지만
허허허 ... 당황 스럽다.
저렇게 위의 사진처럼 0에서 0으로 변경되는 이유는, useCallback
이 count
가 0일 때의 시점을 기준으로 메모리에 함수를 저장했기 때문이다.
이 때문에 어쩐지 useCallback의 두 번째 인자값으로 [] dependency array
가 들어간다고 했었다 .
// initCount가 무엇의 의존해서 새롭게 그려지게 할건지를 생각해 본다면
//count의 값이 변할 때마다 새롭게 그려져야 한다 .
const initCount = useCallback(() => {
console.log(`[COUNT 변경] ${count}에서 0으로 변경되었습니다.`);
setCount(0);
}, [count]); // dependency 배열에 count
내 예상대로 흘러가게 된다.
컴포넌트 안에서 API를 호출하는 코드를 fetchTodo
함수를 만들어주고
useEffect 안에서 호출을 할 때 dependecny array
에 fetchTodo라는 함수가 변경 될 때만 호출되도록 로직을 짠다면
함수도 자바스크립트에서 1급 객체이므로 useEffect 안에서 fetchTodo
가 호출되어 reRendering
이 일어나면 다시 새롭게 fetchTodo
함수가 그려지게 되고 그렇다면 useEffect는 다시 호출되고 이렇게 무한루프에 빠지게 된다....
function App() {
const [todos,setTodos] = useState(null);
const fetchTodo = async () =>{
const {data} = await axios.get('http://localhost:4000/todos')
setTodos(data)
}
}
useEffect(()=>{
fetchTodo()
},[fetchTodo])
//리렌더링 될때마다 fetchTodo자체가 새롭게 그려지므로
// useEffect가 무한대로 실행된다...
이 때 해결 할 수 있는것이 useCallback
으로 함수를 캐싱하는 것이다.
function App() {
const [todos,setTodos] = useState(null);
const fetchTodo = useCallback(()=>{
const {data} = axios.get("http://localhost:4000/todos")
setTodos(data)
},[todos])
useEffect(()=>{
fetchTodo()
},[fetchTodo])
useCallback의 dependancy array
안에 todos가 바뀌면 새롭게 호출하게 끔 하면 무한루프가 해결이 된다.