함수 자체를 메모리에 저장하여, 의존성 배열의 값이 변경될때만 함수를 다시 생성한다.
특히 자식 컴포넌트에 props로 함수를 전달할 때 불필요한 리렌더링을 방지하는데 유용하다.
- useMemo는 값을 저장 이건 함수를 저장
언제 사용? : Memoization된 콜백을 반환할때
즉
const onCreate = useCallback(
() =>{
setData((data)=>[newItem , ...data])
},[a,b]
)
const add = useCallback(() => x + y, [x, y]);
이렇게 되어있으면
- 첫 번째 인자로 있는 함수를 callback 함수로 인지하여 그대로 반환한다.
- 두 번째 인자로 있는 값들이 변하지 않으면 저 함수를 그대로 반환한다는 것이다.
함수 메모이제이션
앞서 말한 useCallback 사용법을 정리하자면 아래와 같다. 첫번째 인자로 넘긴 함수를, 두번째 인자로 넘긴 의존성 배열내의 값이 변경되기 전까지 저장하고 재사용할 수 있게 해준다.
const memoizedFunction = useCallback(함수, 배열);
만약 useCallback을 사용하지 않는다면, 아래와 같은 함수는 컴포넌트가 렌더링 될 때마다 새롭게 생성된다.
const sum = () => x + y;
하지만, useCallback을 사용하면 컴포넌트가 다시 렌더링 되더라도, 해당 함수가 의존하고 있는 값들이 바뀌지 않는다면 함수를 새로 생성하지 않고 기존 함수를 계속 반환한다.
const add = useCallback(() => x + y, [x, y]);
import './App.css'
import Header from './components/Header'
import Editor from './components/Editor'
import List from './components/List'
import { useRef,useReducer,useCallback} from 'react'
function reducer (state, action) {
switch(action.type){
case 'CREATE':
return [action.data, ...state];
case 'UPDATE':
return state.map((item)=>{
return item.id === action.targetId? {...item,isDone : !item.isDone}:item
});
case 'DELETE':
return state.filter((item)=>{
return item.id !== action.targetId
});
default: return state;
}
}
function App() {
const [todos,dispatch] = useReducer(reducer,[]);
const idRef = useRef(0);
// const onCreate=(content)=>{
// dispatch({
// type:"CREATE",
// data : {
// id:idRef.current++,
// isDone:false,
// content: content,
// date: new Date().getTime(),
// },
// })
// }
// 이 함수들은 리렌더링 되도 이제 다시 선언 되지않고 그전에 있던 함수로 대체되나.
const onCreate=useCallback((content)=>{
dispatch({
type:"CREATE",
data : {
id:idRef.current++,
isDone:false,
content: content,
date: new Date().getTime(),
},
})
},[])
const onUpdate = useCallback((targetId)=>{
dispatch({
type:"UPDATE",
targetId:targetId
})
},[])
const onDelete = useCallback((targetId)=>{
dispatch({
type:"DELETE",
targetId:targetId
})
},[])
return (
<div className='App'>
<Header />
<Editor onCreate={onCreate}/>
<List todos={todos} onUpdate={onUpdate} onDelete={onDelete}/>
</div>
)
}
export default App;
함수 동등성
자바스크립트에서 함수는 객체로 취급이 되기때문에, 함수를 동일하게 만들어도 메모리 주소가 다르면 다른 함수로 간주한다. 바로 메모리 주소에 의한 참조 비교가 일어나기 때문인데, 콘솔창에서 아래와 같이 동일한 코드의 함수를 작성하시고 ===
연산자로 비교를 해보면 false
가 반환된다.
const add1 = () => x + y;
undefined
const add2 = () => x + y;
undefined
add1 === add2
false
만약 특정 함수를 다른 함수의 인자로 넘기거나, 자식 컴포넌트의 props로 넘길 때 함수의 참조가 달라서 예상하지 못한 성능 문제가 생길 수 있다. 이 경우, useCallback을 이용해 함수를 특정 조건이 변경되지 않는 이상 재생성하지 못하게 제한하여 함수 동등성을 보장할 수 있다. (만약 리액트가 함수가 동등하지 않다고 판단한다면 상황에 따라 성능이 악화되거나, 무한루프에 빠지는 등의 문제를 겪을 수 있다.)
import React, { useState, useEffect } from "react";
function Profile({ id }) {
const [data, setData] = useState(null);
const fetchData = useCallback(
() =>
fetch(`https://test-api.com/data/${id}`)
.then((response) => response.json())
.then(({ data }) => data),
[id]
);
useEffect(() => {
fetchData().then((data) => setData(data));
}, [fetchData]);
// ...
}
- 이렇게 useCallback 훅을 사용하면, 컴포넌트가 다시 렌더링 되더라도
fetchData
함수의 참조값을 동일하게 유지시킨다. - 따라서, useEffect에 의존성 배열 값에 있는
fetchData
함수는 id 값이 변경되지 않는 한, 재호출되지 않는다.