useCallback
은 useMemo
와 상당히 비슷한 hook이다.
useMemo, useCallback 둘 다 렌더링 성능 최적화에 사용된다.
useMemo
는 어떤 특정한 값이 바뀌지 않으면 이전에 연산했었던 값 을 그대로 가져가는 것이고, useCallback
도 비슷하게 이전에 생성되었던 함수 를 새로 생성하지 않고 계속 사용하게 해주는 함수이다.
함수들은 컴포넌트가 리렌더링 될 때마다 새로 만들어진다
따라서 규모가 큰 프로젝트에서 불필요한 함수 재생성은 리소스를 낭비하기 때문에 최적화를 해주어야 한다.
기본적인 사용 방법은 아래와 같다.
const func=useCallback(()=>{ function or logic },[ value ]);
첫 번째 파라미터에는 생성하고 싶은 함수를 넣고, 두 번째 파라미터에는 배열을 넣는데, 이 배열에는 어떤 값이 바뀌었을 대 함수를 새로 생성해야하는지 명시해야한다.
import React, { useState, useCallback, useRef } from 'react';
const App = () => {
const onInsert = useCallback((text) => {
const todo = {
id:nextId.current,
text,
checkd:false
}
setTodos(todos.concat(todo));
nextId.current+=1;
}, [todos])
const onRemove = useCallback((id)=> {
setTodos(todos.filter(todo=>todo.id !== id));
}, [todos])
const onToggle = useCallback((id)=> {
setTodos(todos
.map(todo=>todo.id === id ? {...todo, checked: !todo.checked} : todo))
}, [todos]);
return (
<TodoTemplate>
<TodoInsert onInsert = {onInsert}/>
<TodoList todos={todos} onRemove={onRemove} onToggle={onToggle}/>
</TodoTemplate>
);
}
따라서 위와 같이 onInsert
, onRemove
, onToggle
함수를 보면 todos
의 값이 바뀔 때 마다 코드의 로직대로 함수가 다시 재생성된다.
함수 내부에서 상태 값에 의존해야 할 때는 그 값을 반드시 두 번째 파라미터에 명시해야한다.
예를 들어, onInsert
는 기존의 todos
를 조회하여 concat()
을 이용해 새로운 배열은 만들기 때문에 todos
에 의존적이라고 할 수 있다. 따라서 todos
를 명시해주어야 한다.
반면 두 번째 파라미터에 아무 것도 넣지 않을 때도 있다. 그렇게 되면 컴포넌트가 리렌더링 될 때 함수가 재생성 되는 일은 없다.
const onChange = useCallback((e)=>{
setName(e.target.value);
}, [])
위와 같은 onChange 이벤트 핸들러를 작성했다고 하면 이 경우, 기존 값을 조회하지 않고 바로 설정만 하기 때문에 두 번째 파라미터가 비어있어도 무관하다.
어떠한 값을 조회한다면 그 값을 무조건 두 번째 파라미터에 명시하자
하위컴포넌트에 props값으로 콜백 함수를 넘길 때, 상위 컴포넌트에서 useCallback을 사용하여 선언한 콜백 함수를 props로 넘기는 것이 유용하다.
함수가 다시 선언 될 때 마다 하위 컴포넌트는 props로 받은 콜백 함수가 달라졌다고 인식하기 때문이다.