useCallback과 useMemo의 차이를 아시나요? - 제대로 답하자 면접질문

타락한스벨트전도사·2024년 10월 4일
35

리액트 훅을 얼마나 잘 이해하고 계신가요? 이번 포스트에서는 useRef와 useReducer를 통해 리액트의 내부 동작을 좀 더 깊이 들여다보고자 합니다. 면접 질문에서도 자주 나오는 useCallback과 useMemo의 차이점을 짚어보며, 리액트의 숨겨진 비밀을 함께 파헤쳐볼까요? 🤔

useCallback과 useMemo의 차이

프론트엔드 개발자라면 한 번쯤 들어본 질문이죠: "useCallback과 useMemo의 차이는 무엇일까요?" 저는 면접 중에 이런 질문을 여러 번 받아봤어요. 😅 보통 "useCallback은 함수를 기억하고, useMemo는 값을 기억한다"고 간단히 대답할 수 있지만, 실상 이 둘의 차이는 그렇게 크지 않답니다. 왜냐하면 자바스크립트의 일급 함수 특성 덕분에 함수 역시 값으로 취급되기 때문이죠.

useMemo(() => {
  return 원하는 함수;
}, [deps]);

위 코드처럼 useMemo로 함수를 반환하면 사실상 useCallback과 큰 차이가 없어요. 이런 점에서 면접관이라면 이렇게 질문할 수도 있을 것 같아요: "useReducer로 useState를 구현해보세요" 또는 "useRef로 useMemo를 구현해보세요"라고요. 이런 질문이 의미가 있는 이유는, 실제 리액트 내부의 구현 방식이 이와 크게 다르지 않기 때문이에요! 😮

리액트 훅의 진짜 모습

모든 리액트 훅 - useRef, useMemo, useState - 이들은 결국 리액트의 내부 상태인 fiber에 붙어 있는 hook.memoizedState를 반환하는 과정일 뿐입니다. setStatedispatch 역시 리액트의 reconciler가 반환하는 형태에 지나지 않아요. 이런 구조를 이해하면, 훅의 동작 방식을 더욱 명확하게 이해할 수 있습니다.

저 역시 리액트를 처음 배웠을 때는 useRef가 특별한 훅이라고 생각했어요. 마치 특정 DOM에 직접 접근하기 위한 용도로만 사용되는 듯 보였죠. 하지만 알고 보니, 이 훅 역시 그저 값을 저장하는 역할을 하는 도구였다는 사실에 놀랐습니다. 🤯

import { useReducer, type SetStateAction } from 'react'

function useState<S>(initialState: S | (() => S)): [S, (action: SetStateAction<S>) => void] {
  const [state, dispatch] = useReducer(
    (state: S, action: SetStateAction<S>): S => 
      typeof action === 'function' ? (action as (prevState: S) => S)(state) : action,
    typeof initialState === 'function' ? (initialState as () => S)() : initialState
  );
  
  return [state, dispatch];
}

import { useRef } from 'react';

function useMemo<T>(factory: () => T, deps: React.DependencyList): T {
  const ref = useRef<{ value: T; deps: React.DependencyList | undefined }>({
    value: undefined as T,
    deps: undefined,
  });

  if (!ref.current.deps || !shallowEqual(deps, ref.current.deps)) {
    ref.current.value = factory();
    ref.current.deps = deps;
  }

  return ref.current.value;
}

function useCallback<T extends (...args: any[]) => any>(
  callback: T,
  deps: React.DependencyList
): T {
  return useMemo(() => callback, deps);
}

function shallowEqual(a: React.DependencyList, b: React.DependencyList) {
  return a.length === b.length && a.every((dep, i) => Object.is(dep, b[i]));
}

shallow와 deep의 차이

useCallback과 useMemo의 의존성 배열(deps)을 비교할 때는 shallow 비교만 수행된다는 사실을 알고 계셨나요? 만약 이를 deepEqual로 바꿔 비교한다면, 어떨까요? 그렇다면 useDeepMemo 같은 새로운 훅을 만들 수 있을 겁니다. 🚀 이를 통해 더 깊은 수준의 동등성 검사를 사용해 최적화를 도모할 수 있겠죠.

나도 리액트를 만들 수 있을까?

리액트의 원리를 이해하는 것은 어렵게 느껴질 수 있지만, 실제로 이를 간단하게 구현해볼 수 있는 기회도 많습니다. 리액트를 직접 만들어보는 것은 리액트의 내부 동작을 이해하는 데 큰 도움이 됩니다. 최근에 재미있게 읽은 몇 가지 포스트들을 공유합니다:

또한, 유튜브 채널 '가장 쉬운 웹개발'의 '리액트 까보기' 시리즈도 리액트의 동작을 이해하는 데 큰 도움이 됩니다. 저 역시 이 시리즈를 통해 리액트를 처음 접했답니다! 😊

리액트를 깊이 이해하는 즐거움

리액트의 내부 동작을 이해하면, 더 효율적이고 강력한 코드를 작성할 수 있습니다. 여러분은 어떻게 생각하시나요? 리액트의 어떤 부분이 가장 흥미롭나요? 댓글로 여러분의 생각을 들려주세요! 💬

리액트 훅과 그 내부 동작에 대해 더 알아가면서, 복잡해 보이는 질문도 점점 익숙해지고, 코드 역시 한층 더 자신감 있게 작성할 수 있을 거예요. #React #FrontendDevelopment #JavaScriptTips #CodingInterview #ReactHooks

profile
스벨트쓰고요. 오픈소스 운영합니다

0개의 댓글