Skip to content
On this page

Implementing advanced usePrevious hook with React useRef

React에서 useRef 의 반환값인 ref는 컴포넌트의 리렌더링을 일으키지 않지만 렌더링 간의 값이 유지하는 변수입니다.

ref의 값인 ref.current 를 수정하면 컴포넌트의 리렌더링은 일어나지 않지만 다른 원인에 의해서 컴포넌트가 리렌더링된다면 해당 snapshot에서는 ref.current 의 최신값을 확인할 수 있습니다.

ref를 이용한 예시 중 하나인 usePrevious hook은 직전 상태값에 접근할 때 사용하고 아래와 같이 구현할 수 있습니다.

jsx
const usePrevious = (value) => {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
};

컴포넌트 리렌더링이 일어날 때마다 ref.current 값은 직전 렌더링 때 값을 참조하지만 리렌더링 이후 DOM에 반영될 시점에 값이 업데이트됩니다.

하지만 위와 같이 구현하면 직전 상태값이 아닌 직전 렌더링 때 값을 참조하게 되어 ref.current 의 값 자체의 변화를 고려하지 않으므로 아래와 같은 수정이 필요합니다.

jsx
export const usePrevious = (value) => {
  // initialize the ref with previous and current values
  const ref = useRef({
    value: value,
    prev: null,
  });

  const current = ref.current.value;

  // if the value passed into hook doesn't match what we store as "current"
  // move the "current" to the "previous"
  // and store the passed value as "current"
  if (value !== current) {
    ref.current = {
      value: value,
      prev: current,
    };
  }

  // return the previous value only
  return ref.current.prev;
};

기존의 useEffect 를 제거하고 컴포넌트 리렌더링 중에 이전 값과 value 인자로 들어온 값을 비교하여 다른 경우에만 ref.current 를 업데이트합니다.

만일 value 인자로 객체가 전달된다면 따로 matcher function을 따로 정의해서 usePrevious hook으로 전달하면 됩니다.

jsx
export const usePrevious = (value, isEqualFunc) => {
  // ...
  if (isEqualFunc ? !isEqualFunc(value, current) : value !== current) {
    // ...
  }

  // ...
};

이제 usePrevious hook의 인자로 props와 같은 reactive value들을 전달하여 사용하면 됩니다.