1-5. useRef란?

강연주·2024년 10월 5일

🙋‍♀️ 기술면접

목록 보기
10/112

5. useRef에 대해 설명해주세요.

useRef는 useState와 동일하게 컴포넌트 내부에서 렌더링이 일어나도
변경 가능한 상태값을 저장한다는 공통점이 있다.

그러나 useState와 구별되는 큰 차이점 두 개가 있다.

  • useRef는 반환값인 객체 내부에 있는 current로 값에 접근 또는 변경할 수 있다.
  • useRef는 그 값이 변하더라도 렌더링을 발생시키지 않는다.

왜 useRef가 필요할까?

렌더링에 영향을 미치지 않는 고정된 값을 관리하게 위해서라면, 함수 외부에서 값을 선언해 관리하는 것이 동일한 기능을 수행할 수 있지 않을까?

🖥️
let value = 0;
function Component() {
  function handleClick() {
    value += 1
  }
  //...
}

이 방식에는 몇 가지 단점이 있다.

  • 컴포넌트가 실행되어 렌더링되지 않았음에도 value라는 값이 기본적으로 존재하여, 메모리에 불필요한 값을 갖게 하는 악영향.
  • Component가 여러 번 생성된다면 각 컴포넌트가 가리키는 값이 모두 value로 동일하다. 컴포넌트가 초기화되는 지점이 다르더라도 하나의 값을 봐야 하는 경우라면 유효할 수도 있지만, 대부분의 경우 컴포넌트 인스턴스 하나 당 하나의 값을 필요로 하는 것이 일반적이다.

➡️ useRef는 상기 두 가지 문제를 극복할 수 있는 리액트식 접근법.
컴포넌트가 렌더링될 때만 생성되며, 컴포넌트 인스턴스가 여러 개라도
각각 별개의 값을 바라본다.


🖥️ useRef를 사용한 DOM 접근 예제

function RefComponent() {
  const inputRef = useRef()

  //이때는 렌더링이 실행되기 전(반환 전)이므로 undefined를 반환
  console.log(inputRef.current) //undefined

  useEffect(() => {
    console.log(inputRef.current) //<input type="text"></input>
  }, [inputRef])

  return <input ref={inputRef} type="text" />
} 

useRef는 최초에 넘겨받은 기본값을 가지고 있다.
명심할 것은, useRef의 최초 기본값은 return문에 정의해 둔 DOM이 아니고 useRef()로 넘겨받은 인수라는 점이다.
useRef가 선언된 당시에는 아직 컴포넌트가 렌더링되기 전이라, return으로 컴포넌트의 DOM이 반환되기 전이므로 undefined다.

useRef를 사용할 수 있는 유용한 경우는, 렌더링을 발생시키지 않고 원하는 상태값을 저장할 수 있다는 특징을 활용해 useState의 이전 값을 저장하는 userPrevious() 같은 훅을 구현할 때다.


🖥️ useRef를 활용한 usePrevious 훅 구현


function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  }, [value]); //value가 변경되면 그 값을 ref에 넣어둔다

  function SomeComponent() {
    const [counter, setCounter] = useState(0);
    const previousCounter = usePrevious(counter);

    function handleClick() {
      setCounter((prev) => prev + 1);
    }

    // 0 (undefined)
    // 1, 0
    // 2, 1
    // 3, 2

    return (
      <button onClick={handleClick}>
        {counter}
        {previousCounter}
      </button>
    );
  }
}

➡️ 이렇게 개발자가 원하는 시점의 값을, 렌더링에 영향을 미치지 않고 보관해두고 싶다면 useRef를 사용하는 것이 좋다.


그렇다면 useRef는 어떻게 구현되어 있을까?

🖥️ React에서의 useRef 구현

export function useRef(initialValue) {
  currentHook = 5
  return useMemo(() => ({current: initialValue}), [])
} 

값이 변경되어도 렌더링되면 안 된다는 점, 실제 값은 {current: value}와 같은 객체 형태로 있다는 점을 떠올려 보자. 렌더링에 영향을 미치면 안 되기 때문에 useMemo에 의도적으로 빈 배열을 선언해 뒀고, 이는 각 렌더링마다 동일한 객체를 가리키는 결과를 낳을 것이다. 자바스크립트의 특징, 객체의 값을 변경해도 객체를 가리키는 주소가 변경되지 않는다는 것을 떠올리면 useMemo로 useRef를 구현할 수 있다.

( 출처 : 모던 리액트 deep dive, 김용찬, 위키북스 )


useRef의 두 가지 역할

1. 저장 공간
: state와 비슷한 역할이나 ref에 저장한 값은 렌더링을 일으키지 않아 내부 변수의 초기화를 막을 수 있음

2. DOM 요소에 접근
: vanilla JS에서 특정 돔을 선택하기 위해 getElmentById 또는 querySelector를 이용하는데, 리액트에서도 특정 돔을 선택해야 하는 경우가 있다. 예를 들어 화면 렌더링 직후 특정 input 태그가 포커싱 되어야 하는 경우 useRef 사용.

https://hanghae99.spartacodingclub.kr/blog/react-%EA%B8%B0%EC%88%A0%EB%A9%B4%EC%A0%91-%EC%98%88%EC%83%81%EC%A7%88%EB%AC%B8-%EB%AF%B8%EB%A6%AC%EB%B3%B4%EA%B8%B02-18266


useRef는 React에서 제공하는 훅(Hook)으로, 컴포넌트에서 DOM 요소나 값을 참조할 수 있도록 하는 기능을 제공합니다. useRef는 다음과 같은 주요 특징을 가지고 있습니다:

1. DOM 요소 접근
useRef를 사용하면 특정 DOM 요소에 접근할 수 있습니다. 예를 들어, 버튼을 클릭했을 때 입력 필드로 포커스를 이동하거나, DOM 요소의 크기와 위치를 참조할 수 있습니다.

import React, { useRef } from 'react';

function TextInputWithFocusButton() {
  const inputRef = useRef(null); // 참조 객체 생성

  const onButtonClick = () => {
    // 버튼 클릭 시 input 요소에 포커스 이동
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </div>
  );
}

위 코드에서 useRef로 생성된 inputRef를 ref 속성으로 input 요소에 전달하여 해당 요소를 참조하게 만들었습니다. inputRef.current를 통해 input 요소에 직접 접근하고 포커스를 이동할 수 있습니다.

2. 값 저장 및 유지 (State 대체)
useRef는 값이 변경되더라도 컴포넌트가 다시 렌더링되지 않는 특성을 가지고 있기 때문에, 값의 변화를 추적하거나 임시 데이터를 저장할 때도 사용할 수 있습니다. 예를 들어, 타이머의 이전 값을 저장하거나 상태 변화를 추적할 때 사용합니다.

import React, { useRef, useState } from 'react';

function TimerComponent() {
  const [count, setCount] = useState(0);
  const countRef = useRef(count); // 현재 count 값 저장

  const increment = () => {
    setCount((prev) => prev + 1);
    countRef.current = countRef.current + 1; // countRef 업데이트
  };

  return (
    <div>
      <p>State: {count}</p>
      <p>Ref: {countRef.current}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

여기서 countRef는 count와 같은 값을 가지지만, setCount로 상태가 변경되어도 컴포넌트가 재렌더링되지 않기 때문에 countRef를 통해 이전 상태나 특정 값을 지속적으로 유지할 수 있습니다.

3. 컴포넌트 간격을 추적하거나 특정 값 캐싱
useRef는 값이 변경될 때마다 새로운 참조 객체를 반환하지 않고 동일한 객체를 유지하므로, 메모이제이션(memoization) 및 컴포넌트가 다시 렌더링될 때 값이 초기화되지 않도록 하는 데 유용합니다.

4. 렌더링 흐름에 영향 없음
useRef로 참조된 값(current)을 수정하더라도, 컴포넌트는 리렌더링되지 않습니다. 이는 React의 상태(useState)와 가장 큰 차이점 중 하나입니다. 상태 값은 변경 시 컴포넌트를 다시 렌더링하지만, useRef는 참조만 할 뿐 컴포넌트의 재렌더링에는 영향을 주지 않습니다.

핵심 요약

  • DOM 요소 참조: 특정 DOM 요소에 접근하거나 조작할 수 있습니다.
  • 값 저장 및 유지: 렌더링 간에도 값을 유지할 수 있습니다.
  • 재렌더링 없이 값 변경: 상태(state)와 달리, 값이 변경되어도 컴포넌트를 리렌더링하지 않습니다.

이런 특성 때문에 useRef는 다음과 같은 상황에서 많이 사용됩니다.

  • 포커스나 스크롤 위치를 조정할 때
  • 애니메이션을 제어할 때
  • 이전 값 추적이나 상태 값 보관이 필요할 때
profile
아무튼, 개발자

0개의 댓글