[Front-end] 개발자 공부

[개발 공부 77일차] React 공식 문서 | Escape Hatches (1)

MOLLY_ 2024. 7. 8. 16:48
728x90

< 목차 >
0. TL;DR
1. ref 사용하는 법
2. ref와 state의 차이

3. refs를 언제 사용해야 할까?

 

 

 

Ref로 값 참조하기 (Referencing Values with Refs)

0. TL;DR

  1. 컴포넌트가 일부 정보를 “기억”하게 하고 싶지만, 해당 정보가 렌더링을 유발하지 않도록 하고 싶을 때 Ref를 사용하자!
  2. ref는 읽거나 변경할 수 있는 current라는 프로퍼티를 호출할 수 있는 자바스크립트 순수객체
  3. ref를 변경하더라도, 리렌더링 되지 않는다.
  4. 렌더링 중에 ref.current를 읽거나 변경하지 말자. 건들면 컴포넌트를 예측하기 어렵게 된다.

 

 

1. ref 사용하는 법

// (1) useRef를 import 해서 ref 추가
import { useRef } from 'react';

export default function Counter() {
	// (2) useRef Hook을 호출하고 참조할 초깃값을 유일 인자로 전달!
	// 다음은 값 0에 대한 ref
  let ref = useRef(0);
  
  // [ref의 추상화] 여기서, useRef는 다음과 같은 객체를 반환함
// {
//   current: 0 // useRef에 전달한 값 
// }

  function handleClick() {
  // (3) `ref.current` 프로퍼티를 통해 해당 ref의 current 값에 접근할 수 있음
  // 이 값은 의도적으로 변경할 수 있으므로 읽고 쓸 수 있음. React가 ‘추적하지 않는’ 구성 요소임
    ref.current = ref.current + 1;
    alert('You clicked ' + ref.current + ' times!');
  }

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

 

ref는 state처럼 문자열, 객체, 심지어 함수 등 모든 것을 가리킬 수 있다. state와 달리 ref는 ‘읽고 수정할 수 있는’ current 프로퍼티를 가진 일반 자바스크립트 객체다.

 

ref를 변경하더라도, 리렌더링 되지 않는다.

 

 

[예시] 스톱워치 구현하기

ref와 state를 단일 컴포넌트로 결합해 보자!

 

사용자가 ‘시작’을 누른 후 시간이 얼마나 지났는지 표시하려면,

  1. 시작 버튼을 누른 시기
  2. 현재 시각을 추적해야 한다.

⇒ 이 정보는 렌더링에 사용되므로 state를 유지한다.

렌더링에 정보를 사용할 때, 해당 정보를 state로 유지한다.

 

import { useState, useRef } from 'react';

export default function Stopwatch() {
	// (1) 시작 버튼을 누른 시기
    const [startTime, setStartTime] = useState(null);\
    
    // (2) 현재 시각 추적
    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() {
	  // `Stop` 버튼을 누르면 now state 변수의 업데이트를 중지하기 위해 기존 interval을 취소해야 함
	  // → 이를 위해 clearInterval을 호출
	  // → 이전에 사용자가 시작을 눌렀을 때 setInterval 호출로 반환된 interval ID를 제공해야 하니까
	  // interval ID는 어딘가에 보관해야 함
	  // → interval ID는 렌더링에 사용되지 않으므로 ref에 저장할 수 있음
    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>
    </>
  );
}

 

 

event handler에게만 필요한 정보이고 변경이 일어날 때 리렌더링이 필요하지 않다면, ref를 사용하는 것이 더 효율적일 수 있다.

 

 

2. ref와 state의 차이

 

 

 

💡 useRef는 내부적으로 어떻게 동작할까?

// React의 내부
function useRef(initialValue) {

  // useRef는 첫 번째 렌더링 중에 { current: initialValue } 을 반환
  // 여기서 state setter가 어떻게 사용되지 않는지 주의하자
  // useRef는 항상 동일한 객체를 반환해야 하므로 필요하지 않음
  const [ref, unused] = useState({ current: initialValue });
  return ref;
}

// 만약, 개발자가 ref.current의 값을 변경할 경우엔 어떻게 될까?
// 아래와 같이 '즉시 변경'된다.
ref.current = 5;
console.log(ref.current); // 5

// 그 이유는 ref 자체가 자바스크립트 객체처럼 동작하기 때문
// 변형하는 객체가 렌더링에 사용되지 않으면 React는 ref 혹은 해당 콘텐츠를 어떻게 처리하든 신경 쓰지 않음
// React: "아 ㅋㅋ 렌더링될 때만 관여할 거라고"

 

 

3. refs를 언제 사용해야 할까?

(1) 일반적인 사용

: 컴포넌트가 React를 ‘외부’와 외부 API(컴포넌트의 형태에 영향을 미치지 않는 브라우저 API)와 통신해야 할 때 ref를 사용

 

 

(2) 특별한 상황에서의 사용

  • timeout IDs를 저장
  • DOM 엘리먼트 저장 및 조작
  • JSX를 계산하는 데 필요하지 않은 다른 객체 저장

 

(3) ref 사용의 좋은 예시

다음의 원칙을 따르면 컴포넌트를 보다 쉽게 예측할 수 있다.

  • refs를 *escape hatch로 간주한다.
    : Refs는 외부 시스템이나 브라우저 API로 작업할 때 유용하다. 하지만, 애플리케이션 로직과 데이터 흐름의 상당 부분이 refs에 의존한다면 접근 방식을 재고해 보는 것이 좋다.

  • 렌더링 중에 ref.current를 읽거나 쓰지 말자.
    : 렌더링 중에 일부 정보가 필요한 경우, state를 대신 사용하자. ref.current가 언제 변하는지 React는 모르기 때문에 렌더링할 때 읽어도 컴포넌트의 동작을 예측하기 어렵다.

 

 

💡 escape hatch

: 프레임워크의 일반적인 규칙이나 제한에서 벗어나, ‘특정한 목적을 달성할 수 있도록 하는 방법이나 기능’

 

리액트에서는 보통 데이터 흐름이 단방향이지만, 때로는 이 규칙을 우회해야 할 필요가 있을 때 escape hatch가 사용된다. 이런 경우, 상태 관리 라이브러리나 리액트 외부의 API를 통해 리액트의 일반적인 데이터 흐름을 우회할 수 있다.

 

 

728x90