본문 바로가기
React

useRef 알아보기

by East-K 2024. 10. 13.

useRef란?

React 공식 문서에서는 useRef를 "렌더링에 필요하지 않은 값을 참조할 수 있게 해주는 React Hook"이라고 정의하고 있습니다.

Vue.js에 익숙한 저는 처음에 '굳이 훅을 사용하지 않고도 constlet을 통해 변수를 선언하면 되는 것 아닌가?'라는 의문이 들었습니다. 그런데 왜 React에서는 useRef라는 Hook을 따로 설계했을까요? 그 이유를 알아보겠습니다.

React에서 useRef가 필요한 이유

React는 컴포넌트가 리렌더링될 때마다 상태와 변수를 다시 초기화하는 특징이 있습니다. 그러나 일부 값은 리렌더링과 상관없이 계속 유지되어야 하는 경우가 있습니다. 예를 들어, DOM 요소에 직접 접근하거나, UI 업데이트와는 무관하게 특정 값을 추적해야 할 때 useRef를 사용합니다. 단순히 let이나 const를 사용해 변수를 선언하면, 리렌더링 시마다 그 값이 다시 초기화되기 때문에 이러한 상황에 적합하지 않습니다.

useRef는 리렌더링이 발생해도 내부적으로 값을 유지할 수 있도록 설계되었습니다. React의 내부 구현 코드(2024-10-13 기준)를 살펴보면memoizedState라는 메커니즘을 통해 useRef로 저장된 값이 리렌더링 간에도 유지된다는 것을 알 수 있습니다.

// packages/react-reconciler/src/ReactFiberHooks.js
function mountRef<T>(initialValue: T): {current: T} {
  const hook = mountWorkInProgressHook();
  const ref = {current: initialValue};
  hook.memoizedState = ref;
  return ref;
}

memoizedState는 리렌더링 시에도 Hook의 상태를 유지하는 역할을 합니다. useRef뿐만 아니라 useStateuseEffect 같은 다른 Hook들도 이 구조를 통해 리렌더링이 되어도 이전 상태를 유지하도록 보장됩니다. 즉, memoizedState에 저장된 값은 리렌더링이 발생하더라도 초기화되지 않으며, 계속해서 유지됩니다.

useRef와 일반 변수(let)의 차이

다음 코드 예시는 useReflet 변수가 리렌더링에서 어떻게 다르게 동작하는지를 보여줍니다.

export default function Count() {
  const useRefCount = useRef(0);
  const handleUseRefCount = () => {
    useRefCount.current += 1;
    console.log(useRefCount);
  }

  let count = 0;
  const handleCount = () => {
    count += 1;
    console.log(count);
  }

  const [trigger, setTrigger] = useState(false);

  return (
    <div>
      <div>
        useRef count: { useRefCount.current }
      </div>
      <button onClick={handleUseRefCount}>useRef Count plus</button>
      <div>
        count: {count}
      </div>
      <button onClick={handleCount}>Count plus</button>
      <button onClick={() => setTrigger(!trigger)}>rerendering Trigger</button>
    </div>
  );
}

이 코드를 실행하면, useRef Count plus 버튼과 Count plus 버튼을 눌렀을 때 콘솔에는 값이 증가하지만 UI 상에는 값이 변하지 않는 것을 볼 수 있습니다. 하지만 Rerender Trigger 버튼을 눌러 리렌더링을 발생시키면, useRefCount는 증가한 값을 유지하는 반면, count는 다시 0으로 초기화됩니다.

  • useRefCount.current: 리렌더링이 발생해도 증가한 값을 유지.
  • let count: 리렌더링 시 초기화됨.

이는 useRef의 상태가 memoizedState에 저장되어 리렌더링 중에도 값이 유지되는 반면, let count는 컴포넌트가 다시 렌더링되면서 초기화되기 때문입니다.

memoizedState의 역할

memoizedState는 각 Hook의 상태를 기억하고, 리렌더링이 발생하더라도 그 상태를 유지하는 중요한 역할을 합니다. useRef를 통해 관리되는 값은 렌더링에 영향을 주지 않으면서도 리렌더링 간 값을 계속 유지하기 때문에, 상태가 UI 업데이트와는 무관하지만 리렌더링 간에도 저장이 필요한 경우에 매우 유용합니다.

반면 let이나 const로 선언된 변수는 리렌더링 시 컴포넌트가 다시 실행되면서 초기화되므로 값을 유지할 수 없습니다. 결국 memoizedState 덕분에 React는 리렌더링 시에도 Hook의 상태를 안정적으로 관리할 수 있게 됩니다.

결론

결론적으로, useRef렌더링에는 영향을 미치지 않으면서도 리렌더링이 발생해도 내부적으로 값을 유지할 수 있는 Hook입니다. 이를 통해 DOM 요소에 접근하거나, 컴포넌트의 상태와는 별개로 값을 유지해야 하는 경우에 매우 효과적으로 사용할 수 있습니다. 예를 들어:

  • HTML 요소에 직접 접근해야 할 때
  • 타이머 ID, 폼 입력값 추적 등 렌더링과 무관한 값을 저장해야 할 때

이와 같은 상황에서 useRef를 적절히 활용할 수 있습니다.

'React' 카테고리의 다른 글

React-Router 기본 원리 알아보기  (0) 2024.11.18
useCallback 알아보기  (2) 2024.10.14
useState 깊게 분석하기  (6) 2024.10.10