본문 바로가기

React

[ React ] useRef를 알아보자

반응형

 

이 글은 리액트 공식 문서와 이 영상을 참고해서 정리한 글이다. 영상이 아주 잘 정리되어 있어서 보는 것을 강력히 추천한다.

(영상제작자에게 허락 받았습니다)

 

정의 및 특징

우선 공식 문서에서 useRef를 어떻게 정의하고 있는지부터 알아보자.

 

useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.

 

여기서 중요한 것은 세 가지다.

 

1. useRef는 변경 가능한 객체를 반환한다.

2. 반환 객체는 useRef의 인자를 'current'라는 속성에 저장한다.

3. 반환 객체는 컴포넌트의 lifetime동안 지속된다. 

 

여기서 3번이 어떤 의미인지 잘 와닿지 않을 수 있다. 'lifetime동안'은 'unmount 될때까지'로 대체할 수 있다. 즉, ref는 컴포넌트가 unmount 될때까지 지속된다는 뜻이다. 

 

위 세 가지가 'useRef'의 정의라면 아래 두 가지는 'useRef'의 특징이다. 공식문서에서 그대로 복붙했다. 

 

useRef will give you the same ref object on every render.
Mutating the .current property doesn’t cause a re-render.

 

번역하면 아래와 같다.

 

1. Ref가 변해도 re-render 되지 않는다

2. re-render 되어도 Ref는 유지된다. 

 

useRef에 대해서는 볼드 처리한 다섯 가지만 기억하고 있으면 된다. 이제 예제와 활용 예시를 통해 useRef를 왜, 언제 쓰는지 알아보자. 

 

예제

예제 UI

더보기
import React, { useState, useRef } from 'react';

const App = () => {
  const [state, setState] = useState(0);
  const countRef = useRef(0);
  let countVar = 0;

  console.log(countRef);
  console.log('rendering...');

  // state 증가 함수
  const increaseState = () => {
    setState(state => state + 1);
    console.log(`state: ${state}`);
  }

  // Ref 증가 함수
  const increaseRef = () => {
    countRef.current += 1;
    console.log(`ref: ${countRef.current}`);
  }

  // 일반 변수(var) 증가 함수
  const increaseVar = () => {
    countVar += 1;
    console.log(`var: ${countVar}`)
  }

  return (
    <div style={{padding: '50px'}}>
      <p>State: {state}</p>
      <p>Ref: {countRef.current}</p>
      <p>Var: {countVar}</p>
      <button onClick={increaseState}>State</button>
      <button onClick={increaseRef}>Ref++</button>
      <button onClick={increaseVar}>Var++</button>
    </div>
  )
}

 

접은글을 펼치면 코드를 확인할 수 있다. 아주 간단한 코드다. 우선 처음 페이지가 로드될 때, 콘솔창을 보면 다음과 같을 것이다. 

 

처음 페이지가 로드될 때의 콘솔창

 

첫 번째 객체는 countRef를 출력한 값이다. 아까 살펴본대로 current 속성에 initialValue(=0)이 들어간 것을 확인할 수 있다. 'rendering...'은 컴포넌트 함수가 호출되었다는 의미다. 

 

이제 Var → Ref → State 순서로 버튼을 눌러보자. 

Var, Ref, State 버튼을 순서대로 클릭했을 때의 결과

 

결과가 예상과 일치하는지 확인해보자. 만약에 일치했다면 useRef를 이해한 것으로 봐도 무방하다. 각 버튼을 클릭했을때 어떤 일이 일어나는지 분석해보자. 

 

1) Var 버튼 클릭

일반 변수 countVar의 값이 1 증가한다. state 또는 props가 변하지 않았으므로 re-render 되지 않는다.

 

2) Ref 버튼 클릭

countRef.current의 값이 1 증가한다. 역시 같은 이유로 re-render 되지 않는다. 

 

3) State 버튼 클릭

상태가 변했으므로 re-render 된다. 따라서 컴포넌트 내부 변수들이 초기화된다. 따라서 countVar는 0으로 초기화 된다. 그러나 countRef는 unmount 전까지 유지되므로 Ref에 1이 찍힌다.

 

 

증가한 값이 화면에 반영되기 위해서는 무조건 re-render 되어야 한다. Var, Ref 버튼을 클릭해도 화면에 아무 변화가 없었던 것은 re-render 되지 않았기 때문이다. 하지만 이벤트 핸들러 함수에 콘솔로 찍어놨기 때문에 콘솔창에서는 변화를 확인할 수 있다. 아까 버튼을 하나씩 클릭하고 마지막 state 버튼을 클릭한 직후의 콘솔창은 아래와 같다. 

 

Var - Ref - State 버튼 클릭 이후의 콘솔창

 

var와 ref 값은 증가했지만, 렌더링 되지 않아서 화면에 반영 되지 않았던 것 뿐이다. state가 0으로 찍히는 이유는 setState 함수가 비동기적으로 실행되기 때문이다. 화면에는 1 증가한 값이 반영된다. 

 

활용 예시 1 - 타이머 예시

더보기
import React, { useState, useRef, useEffect } from 'react';

const App = () => {
  const timerIdRef = useRef(0);
  const [count, setCount] = useState(0);
  const startHandler = () => {
    if (timerIdRef.current) { return; }
    timerIdRef.current = setInterval(() => setCount(c => c+1), 1000);
  };
  const stopHandler = () => {
    clearInterval(timerIdRef.current);
    timerIdRef.current = 0;
  };
  useEffect(() => {
    return () => clearInterval(timerIdRef.current);
  }, []);
  return (
    <div style={{padding:'40px'}}>
      <div>Timer: {count}s</div>
      <div>
        <button onClick={startHandler}>Start</button>
        <button onClick={stopHandler}>Stop</button>
      </div>
    </div>
  );
}

 

접은글을 펼치면 코드를 확인할 수 있다. useRef를 활용해서 타이머를 구현한 멋진 예제다. 'start' 버튼을 누르면 타이머가 시작되고, 'stop' 버튼을 누르면 타이머가 멈춘다. 멈춘 상태에서 다시 'start' 버튼을 누르면 멈췄던 시간에서 이어서 증가한다.

 

로직은 생각보다 간단하다. 매초마다 state를 증가하는 setInterval 비동기 함수를 timerIdRef에 저장해놓고, start를 눌렀을 때 실행시키고, stop을 눌렀을때 clearInterval 함수로 종료시킨다. 매초마다 re-render 되므로 컴포넌트 내부 변수들은 초기화 되지만, timerIdRef는 useRef 훅으로 인해 값이 유지된다. 추가로 이미 시작한 상태에서 start를 누르면 if문에 의해 무시되는 스킬은 나중에 쓸모 있을거 같으니 기억하면 좋을 것 같다.

 

이처럼 렌더링에 구애받지 않는 변수를 사용하고 싶으면 useRef가 훌륭한 대안이 될 수 있다. 그냥 일반 함수에서 let, const로 변수를 선언했는데 이 변수값을 렌더링마다 유지시키고 싶다? useRef를 쓰자!

 

활용 예시 2 - 자동 포커싱

useRef를 가장 많이 활용하는 예시다. 코드가 길지 않으니 직접 살펴보자. 

const App = () => {
  const inputRef = useRef();
  useEffect(() => {
    inputRef.current.focus();
  }, []);
  return (
    <input 
      ref={inputRef} 
      type="text" 
    />
  );
}

 

페이지가 로드되면 useEffect 훅에 의해 input 태그에 자동 포커싱 되는 코드다. input 태그 안에 있는 'ref' 속성은 리액트의 예약어다. 이는 마치 리스트 컴포넌트에 꼭 기입해줘야 하는 'key'과 비슷하다. ref 속성은 참조 속성으로, DOM node 또는 리액트 element에 사용 가능하다. 목적이 '참조'이므로 다이렉트로 접근할 때 사용하는 속성이다. 위 예시는 input 태그에 다이렉트로 접근한 경우다. 

 

 

 

<참조>

https://dmitripavlutin.com/react-useref-guide/   

https://www.youtube.com/watch?v=VxqZrL4FLz8   

https://reactjs.org/docs/hooks-reference.html#useref   

 

 

 

반응형