코딩을 쉽게 해보자

[React-beta] Escape Hatches - Ref로 값 참조하기 [번역] 본문

React

[React-beta] Escape Hatches - Ref로 값 참조하기 [번역]

꿀단지코딩 2023. 2. 9. 15:55

https://beta.reactjs.org/learn/referencing-values-with-refs

 

Referencing Values with Refs

A JavaScript library for building user interfaces

beta.reactjs.org

 

컴포넌트가 어떠한 정보를 "기억"하기를 바라고 그 정보가 새로운 렌더링을 일어나지 않길 원한다면, ref를 쓸 수 있다.

 

컴포넌트에 ref 추가하기

useRef 훅을 React에서 import하면 쓸 수 있다.

 

컴포넌트 안에서 useRef훅을 가져와 참조하고 싶은 초기값을 전달하면

const ref = useRef(0);

// useRef는 이러한 object를 반환한다.
{
  current: 0 // The value you passed to useRef
}

 

현재 값을 ref.current 프로퍼티로 접근할 수 있다.

값은 의도적으로 변경될 수 있고 일고 쓸수 있다.

React가 추적하지않는 비밀의 주머니와 같다. (이것이 escape hatch를 React의 데이터 흐름으로부터 만드는 것이다)

 

import { useRef } from 'react';

export default function Counter() {
  let ref = useRef(0);

  function handleClick() {
    ref.current = ref.current + 1;
    alert('You clicked ' + ref.current + ' times!');
  }

  return (
    <button onClick={handleClick}>
      Click me!
    </button>
  );
}

 

ref가 숫자를 가리키고 있다. state처럼 string, object, function을 가리킬 수 있다.

state와는 다르게 ref는 current property를 읽고 수정할 수 있는 순수한 JS 객체다.

 

예시: 스톱워치 만들기

ref와 state를 하나의 컴포넌트에서 결합할 수 있다.

유저가 버튼을 눌러 시작하고 멈출 수 있는 스톱워치를 만들어보자.

스타트버튼을 누른지 얼마나 시간이 지났는지 보여주려면,

스타트버튼이 언제 눌러졌는지 그리고 현재 시간이 어떻게 되는지 추적해야 한다.

이 정보는 렌더링을 위해 사용되니, state로 관리한다.

 

유저가 Start를 누르면, setInterval을 사용해 10 밀리세컨드마다 업데이트한다.

import { useState } from 'react';

export default function Stopwatch() {
  const [startTime, setStartTime] = useState(null);
  const [now, setNow] = useState(null);

  function handleStart() {
    // Start counting.
    setStartTime(Date.now());
    setNow(Date.now());

    setInterval(() => {
      // Update the current time every 10ms.
      setNow(Date.now());
    }, 10);
  }

  let secondsPassed = 0;
  if (startTime != null && now != null) {
    secondsPassed = (now - startTime) / 1000;
  }

  return (
    <>
      <h1>Time passed: {secondsPassed.toFixed(3)}</h1>
      <button onClick={handleStart}>
        Start
      </button>
    </>
  );
}

 

Stop버튼을 누르면, 현재 존재하는 interval을 취소해

now state 변수를 업데이트 하는 것을 멈춘다.

이것을 clearInterval을 불러옴으로써 할 수 있다.

 

interval ID가 렌더링을 사용되지 않기 때문에, ref로 관리할 수 있다.

import { useState, useRef } from 'react';

export default function Stopwatch() {
  const [startTime, setStartTime] = useState(null);
  const [now, setNow] = useState(null);
  const intervalRef = useRef(null);

  function handleStart() {
    setStartTime(Date.now());
    setNow(Date.now());

    clearInterval(intervalRef.current);
    intervalRef.current = setInterval(() => {
      setNow(Date.now());
    }, 10);
  }

  function handleStop() {
    clearInterval(intervalRef.current);
  }

  let secondsPassed = 0;
  if (startTime != null && now != null) {
    secondsPassed = (now - startTime) / 1000;
  }

  return (
    <>
      <h1>Time passed: {secondsPassed.toFixed(3)}</h1>
      <button onClick={handleStart}>
        Start
      </button>
      <button onClick={handleStop}>
        Stop
      </button>
    </>
  );
}

정보의 일부분이 렌더링을 위해 사용되었다면, state로 관리하자.

정보의 일부분이 event handler를 위해서만 사용되었고 이 일부분이 변화하는 것이 리렌더를 필요로 하지 않는다면,

ref가 좀 더 효과적일 것이다.

 

refs와 state의 차이

refs가 state보다 좀 더 엄격해 보일 수 있다.

언제나 state setting function을 사용하는 것 보다 refs를 변경할 수 있다.

하지만 많은 경우에서는 state를 사용할 것이다.

Refs는 "escape hatch"이기 때문에 자주 필요로 하지 않을 것이다.

refs state
useRef(initialValue)
returns { current: initialValue}
useState(initialValue)
returns 현재 state의 값 변수와 state setter funciton
값을 바꾸면 리렌더링이 일어나지 않는다. 값을 바꾸면 리렌더링이 일어난다.
mutuable이다. 수정하거나 업데이트를 current's 값으로 렌더링 과정 밖에서 할 수 있다. immutable이다.
무조건 state setting function으로 state 변수를 수정해 리렌더를 queue에 넣는다.
렌더링 도중 current value를 읽거나 쓰지 말아야한다. 언제나 state를 읽어도 되지만
하나의 렌더마다 state의 변하지 않는 '스냅샷'이 있다.

 

state로 구현된 counter 버튼이 있다.

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <button onClick={handleClick}>
      You clicked {count} times
    </button>
  );
}

count 값이 화면에 보여지기 때문에 state value를 쓰는 것이 합리적으로 보인다.

setCount로 값을 바꿀 때, React는 컴포넌트를 리렌더하고 화면은 새로운 count를 업데이트해 보여준다.

 

만약에 ref로 구현하려고 하면, 리액트는 절대로 다시 컴포넌트를 리렌더하지 않을것이다.

count가 변화하는 것을 보지 못한다.

 

이 것이 렌더링 도중 ref.current을 읽는 것을 신뢰할 수 없는 코드로 만드는 것이다.

필요하면 state를 사용해야 한다.

 

Deep Dive: useRef는 내부는 어떻게 동작하는가?

useRef는 useState 위에서 구현되있다고 상상할 수 있다.

// Inside of React
function useRef(initialValue) {
  const [ref, unused] = useState({ current: initialValue });
  return ref;
}

처음 렌더할 때, useRef는 { current: initialValue }를 return한다.

이 객체는 React에 저장되어 있으므로, 다음 렌더링 중 같은 객체가 반환될 것이다.

state setter가 unused가 이 예시에서 안쓰이고 있는 것을 확인해야 한다.

useRef가 언제나 같은 객체를 return하기 때문에 불필요한 것이다.

 

React는 빌트인 버전의 useRef를 제공한다. 왜냐하면 실제로 충분하기 때문이다.

만약 객체지향 프로그래밍에 익숙하다면, refs는 instances fields가 기억나게 할 것이다.

this.something 대신에 somethingRef.current 말이다.

 

언제 refs를 써야 하는가

일반적으로, React에서 컴포넌트가 "한 발짝 벗어나(step outside)"기 필요할 때 

바깥 API와 소통하기 위해 ref를 사용한다.

보통 보여지는 컴포넌트에 영향을 주지 않는 브라우저 API가 그렇다.

여기 드문 상황들이 있다.

  • timeout IDS를 관리하기
  • 다음 페이지에 사용될 DOM elements를 관리하거나 조작하기
  • JSX를 계산하기 위해 필요하지 않는 객체들을 저장하기

 

refs의 모범적인 사례

이 원칙들을 따르면 컴포넌트들을 더 예측가능하게 만들어 준다.

  • refs를 escpae hatch로 생각하자. Refs는 밖의 시스템이나 브라우저 API를 다룰 때 유용하다. 만약 어플리케이션의 많은 로직 부분과 데이터 흐름이 refs에 의존한다면, 접근 방법을 다시 생각해봐야 할 수도 있다.
  • 렌더링 도중 ref.current를 읽거나 쓰지말자. 만약에 렌더링 도중 어떠한 정보가 필요하다면, state를 사용하자. React가 ref.current의 값이 언제 변하는지 모르기 때문에, 렌더링 도중 읽는 것은 컴포넌트가 예측하기 어려워진다.
    (오직 예외적인 것은 if (!ref.current) ref.current = new Thing()과 같이 ref를 처음 렌더링 때 설정하는 것과 같다.

React의 state의 한계점들이 refs에는 적용되지 않는다.

state는 매 렌더마다 스냅샷처럼 동작하고, 동기적으로 동작하지 않는다.

하지만 ref의 현재 값을 수정하면 바로 변화된다.

ref.current = 5;
console.log(ref.current); // 5

이것은 왜냐하면 ref 자체가 JS object이기 때문이다.

 

ref가 수정되는 것에 대해 걱정하지 않아도 된다. 수정하더라도 매 랜더링마다 사용되지 않는다면, React는 신경쓰지 않기 때문이다.

 

Ref와 DOM

ref로 어떤 값이라도 가르킬 수 있다.

하지만 일반적으로 DOM element에 접근할때 사용된다.

JSX 어트리뷰트에 ref를 전달하면

<div ref={myRef}>

React는 해당 DOM element를 myRef.current로 놓을 것이다.

 

요약

  • ref는 렌더링에 필요하지 않는 값을 다루기 위한 escpae hatch다. 그렇기 많이 필요로 하지 않다.
  • ref는 일반 JS 객체로 하나의 current라는 읽고 수정할 수 있는 프로퍼티를 가졌다. 
  • useRef 훅을 React에서 부르므로 쓸 수 있다.
  • state처럼 컴포넌트의 리렌더링 사이 정보를 유지하게 해준다.
  • state와 달리 ref의 current 값을 바꾸는 건 리렌더를 일으키지 않는다.
  • ref.current를 렌더링 도중 읽거나 쓰지 말아야한다. 컴포넌트를 예측하기 어렵게 만든다.

'React' 카테고리의 다른 글

[React-beta] Escape Hatches - Ref로 DOM 다루기 [번역]  (0) 2023.02.10