[React] useCallback

ZenTechie·2023년 6월 4일
0

React

목록 보기
3/3
post-thumbnail

이전의 React.memo에서 분명 함수는 변하지 않고 코드상으로는 동일한 함수를 호출하는 것처럼 보이는데, re-rendering 되었을 때 함수가 재생성되는 것을 보았다.

이는 코드 상으로는 동일해보이지만, 내부적으로는 다르다는 것을 얘기했었다.
또한, React.memo배열, 객체, 함수에서는 사용할 수 없특정 결과값(컴포넌트 등)을 재사용 할 때 사용한다고 했다.

이번에 알아볼 useCallback은, React.memo와 비슷한 Hook이지만 특정 함수를 새로 만들지 않고 재사용하고 싶을때 사용한다.

What is useCallback?

useCallback 이란, 컴포넌트 실행 전반에 걸쳐 함수cache할 수 있게 해주는 (Hook)이다. 여기서 cache저장을 의미한다.

그렇다면 useCallback어떻게 사용할까?

const cachedFn = useCallback(fn, dependencies)

인자는 총 2개로 구성된다.

  • fn: cache하고 싶은 함수.
    리렌더링 됐을 때, 마지막 렌더링 이후로 dependencies가 변경되지 았았다면 리액트는 같은 함수를 제공한다. 즉, 함수를 재사용할 수 있다는 것이다.
    (✅ 중요한 것은 리액트가 함수를 호출(call)하는게 아닌 제공(give)한다는 것)

  • dependencies: useCallback 호출에 대한 의존성 배열이며, fn에서 사용되는 모든 값의 리스트(배열)를 의미한다. 여기서 값이라는 것은 props, state, contextfn에서 사용되는 모든 것들을 의미한다. (의존성은 useEffect에서 사용되는 것과 의미가 동일하다.)

사용 예시

const toggleParagrahHandler = useCallback(() => {
  setShowParagraph((prevShowParagraph) => !prevShowParagraph);
}, []);

언제 사용할까?

  1. 컴포넌트의 리렌더링(re-rendering)을 막고 싶을 때
  2. memoized callback이전 state를 기반으로 현재 state업데이트할 때
  3. Effect너무 자주 발생하지 않도록 방지하고 싶을 때
  4. 커스텀 훅(custom Hook)을 최적화하고 싶을 때

2, 3번의 경우를 살펴보자.

memoized callback의 이전 state를 기반으로 현재 state를 업데이트할 때

function TodoList() {
  const [todos, setTodos] = useState([]);

  const handleAddTodo = useCallback((text) => {
    const newTodo = { id: nextId++, text };
    setTodos([...todos, newTodo]);
  }, [todos]);
  // ...

위는 todos다음 상태를 업데이트하는 코드이다. 의존성 배열에 todos를 넘겼다.

여기서 의존성 배열을 가능하면 최대한 적게 넘기고 싶을 수 있다. 만약, 다음 state를 계산하기 위해서 현재 state를 읽기만 한다면, updater function을 넘겨서 의존성을 삭제할 수 있다.

function TodoList() {
  const [todos, setTodos] = useState([]);

  const handleAddTodo = useCallback((text) => {
    const newTodo = { id: nextId++, text };
    setTodos(todos => [...todos, newTodo]);
  }, []); // ✅ No need for the todos dependency
  // ...

위는 의존성인 todos를 삭제하고 state를 업데이트 하는 (todos => [...todos, newTodo])를 사용했다.

Effect가 너무 자주 발생하지 않도록 방지하고 싶을 때

가끔 아래 코드와 같이, Effect 내부함수를 호출 해야하는 경우가 있을 수 있다.

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  function createOptions() {
    return {
      serverUrl: 'https://localhost:1234',
      roomId: roomId
    };
  }

  useEffect(() => {
    const options = createOptions();
    const connection = createConnection();
    connection.connect();
	return () => connection.disconnect();
  }, [createOptions]); // 🔴 Problem: This dependency changes on every render
  // ...

위 코드에서 createOptions의존성으로 넘긴다면, 매 렌더링마다 chat room에 계속해서 재연결하게 될 것이다.

이를 방지하기 위해서는 다음과 같이 코드를 수정해야 한다.

const createOptions = useCallback(() => {
    return {
      serverUrl: 'https://localhost:1234',
      roomId: roomId
    };
  }, [roomId]); // ✅ Only changes when roomId changes

  useEffect(() => {
    const options = createOptions();
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, [createOptions]); // ✅ Only changes when createOptions changes
  // ...

이는, roomId가 동일하다면 createOptions 함수는 완전히 동일함을 보장한다.
그리고 roomId가 변경되지 않는 한, createOptions재생성되지 않는다.(즉, chat room에 계속해서 재연결하지 않는다.)

그런데 여기서 굳이 useCallback을 사용하지 않고 useEffect 내부로 함수를 옮기고, 의존성 배열roomId를 넘기면, 위의 코드와 완전히 동일한 기능을 할 수 있다.

useEffect(() => {
    function createOptions() { // ✅ No need for useCallback or function dependencies!
      return {
        serverUrl: 'https://localhost:1234',
        roomId: roomId
      };
    }

    const options = createOptions();
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]); // ✅ Only changes when roomId changes
  // ...

결론

결론적으로 useCallback은 다음의 역할을 수행한다.

  • 우리가 선택한 함수(fn)를 리액트의 내부 저장 공간저장해서 함수 객체가 실행될 때 마다 이를 재사용할 수 있게 해준다.
  • 만약 어떤 함수가 절대 변경되어서는 안된다면, useCallback사용함수를 저장하면 된다.
profile
데브코스 진행 중.. ~ 2024.03

0개의 댓글