본문 바로가기

Y ticle

[React] Ref 는 언제 사용되고 왜 필요한걸까

When you want a component to “remember” some information, but you don’t want that information to trigger new renders, you can use a ref.

리액트에서 컴포넌트를 설계하다 보면 특정 컴포넌트의 렌더링을 제한해야할때가 있습니다.

이런 케이스가 종종 있지 않나요?

컴포넌트가 현재의 특정한 정보를 기억하면서(remember) 그 정보가 새 렌더링을 촉발하지 않도록 구현해야하는 경우가 있습니다.

ref를 이용하면 가능합니다.

React Ref 어트리뷰트란?

리액트 ref는 리액트 요소에 대한 참조를 생성하는데 사용되는 기능입니다.

ref가 유용히 사용되는 일반적인 경우를 3가지로 나누어서 설명하겠습니다.

1) DOM 요소에 대한 접근

2) 외부 라이브러리 통합

3) 애니메이션 및 포커스 제어

ref를 통해 DOM 요소나 컴포넌트에 직접 접근할 수 있습니다.

DOM 요소에 접근

리액트 컴포넌트가 렌더링된 후에 해당 컴포넌트 내부의 특정 DOM 요소에 접근해야할때 ref를 사용할 수 있습니다.

이미 렌더링된 컴포넌트에 리렌더링을 유발하지 않으면서도 DOM 요소의 크기/위치를 확인하거나 조작할 수 있겠죠.

예시로 특정 input 요소의 값을 변경하거나 가져올때 쉽게 사용할 수 있겠죠?

export const MyModal = ({
  children,
  opened,
  onClose,
  onClickBackdropAndClose = true,
  drawer = false,
}: Props) => {
  const dialogRef = useRef<HTMLDialogElement>(null);

  useEffect(() => {
    if (opened) {
      ref.current.showModal();
    } else {
      ref.current.close();
    }
  }, [opened]);

  const onClickBackdrop = (e: React.MouseEvent<HTMLDialogElement, MouseEvent>) => {
    if (onClickBackdropAndClose) {
      const target = e.target as HTMLDialogElement;
      if (target === ref.current) {
        onClose();
****      }
    }
  }

  return (
    <Dialog
      ref={dialogRef}
      onClick={onClickBackdrop}
      onClose={onClose}
      drawer={drawer}
    >
      {children}
    </Dialog>
  );
}

외부 라이브러리 통합

리액트 컴포넌트에서 외부 라이브러리를 이용해서 그 라이브러리 컴포넌트와 상호작용(조작) 해야할때 ref를 사용할 수 있습니다.

예를 틀어 차트 혹은 맵 라이브러리를 가져다 쓸때 리액트 컴포넌트와 DOM 요소의 연결을 설정하기 위해서 ref를 사용할 수 있습니다.

import React, { useEffect, useRef } from 'react';
import Chart from 'chart.js/auto';

const MyChart = () => {
  const chartRef = useRef<HTMLCanvasElement>(null);

  useEffect(() => {
    if (!chartRef.current) return;

    const ctx = chartRef.current.getContext('2d');
    if (ctx) {
        new Chart(ctx, {
          type: 'bar',
          data: {
            labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
            datasets: [{
              label: '# of Votes',
              data: [12, 19, 3, 5, 2, 3],
            }],
          },
        });
      }
    }
  }, []);

  return <canvas ref={chartRef} />;
};

export default MyChart;

애니메이션 및 포커스 제어

ref를 이용해서 컴포넌트 state를 유지하면서도 애니메이션 및 트랜지션 효과를 적용하거나 제어할 수 있습니다. 포커스를 조작하기 위해서도 자주 사용됩니다.

예를 들어 컴포넌트가 화면에 나타날 때 애니메이션을 적용하기도 하고, 화면에서 사라질때 트랜지션 효과를 적용하기도 합니다.

import React, {useRef} from 'react';

const MyInputComponent = ({validate}:{validate:(str:string) => boolean}) => {
    const inputRef = useRef<HTMLInputElement>(null);
    const router = useRouter();

    cosnt onClick = () => {
        if(!inputRef.current)return;
        if(!validate(inputRef.current.value)){
            inputRef.current.focus(); //입력 필드에 포커스 설정
            return;
        }

        router.push('/');
    };

    return (
        <div>
            <input type="text" ref={inputRef}/>
            <button onClick={handleClick}>confirm</button>
        </div>
    )
} 

왜 React는 Ref로 DOM 요소에 접근하는 것일까

1) 리액트는 가상 돔(virtual DOM)을 이용합니다.

2) 리액트는 선언형 프로그래밍(declarative programming)을 지향합니다.

3) 리액트는 컴포넌트 기반 아키텍처를 취합니다.

리액트는 가상 돔(virtual DOM)을 이용합니다.

리액트는 가상 DOM을 통해서 성능을 향상시킵니다.

가상 DOM은 실제 DOM에 대응되는 JS 객체로 가상 DOM을 통해 실제 DOM 조작을 최적화합니다.

하지만 가상 DOM은 실제 DOM 요소에 직접 접근할 수 없기에 ref 어트리뷰트를 이용해 실제 DOM에 접근하여 실제 DOM 요소를 참조하고 조작합니다.

일반적으로 HTML에서 DOM 요소에 고유 이름을 추가하기 위해서는 id를 사용합니다. 하지만 id는 유일해야하고 컴포넌트를 재사용하게 된다면 중복 가능성이 높습니다. ref를 사용하면 id와 달리 전역적이지 않고 컴포넌트 내부에서만 유효하게 동작합니다.

리액트는 선언형 프로그래밍을 지향합니다.

리액트 최초 공식 홈페이지에는 다음과 같이 리액트를 소개하고 있습니다.

Declarative

React makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes.

Declarative views make your code more predictable and easier to debug.

리액트는 선언적 프로그래밍 접근 방식을 채택하여 UI를 설계합니다.

이는 state와 UI를 동기화하여 UI가 상태에 따라 자동으로 업데이트 되는 방식을 의미합니다.

따라서 DOM 요소에 직접 접근하여 조작하는 것은 리액트의 이러한 철학과 어긋날 수 있습니다.

하지만 JS 생태계의 많은 라이브러리가 DOM 요소에 직접적으로 접근하여 동작합니다. 이런 외부 라이브러리를 이용하고 제한적으로 직접 DOM을 조작하는 것을 허용하기 위해 ref를 사용하도록 권장됩니다.

리액트는 컴포넌트 아키텍처를 기반으로합니다.

Component-Based

Build encapsulated components that manage their own state, then compose them to make complex UIs.

Since component logic is written in JavaScript instead of templates, you can easily pass rich data through your app and keep state out of the DOM.

리액트는 컴포넌트 기반 아키텍처를 채택하여 UI를 구축합니다.

이를 통해 UI를 독립적으로 재사용 가능한 단위로 분리하고 유지보수를 용이하게 합니다.

컴포넌트는 각각의 상태(state)와 라이프라이클을 가집니다.

ref를 통해 제한적으로 DOM 요소에 접근하여 해당 컴포넌트 로직을 외부에서 조작할 수 있습니다.

ref를 이용하여 DOM을 조작하면 특정 컴포넌트의 리렌더링을 방지하며 state를 유지할 수 있습니다.

따라서 하나의 앱을 구성하기 위해 여러 블록처럼 이용된 컴포넌트들의 리렌더링을 용이하게 조율하면서도 특정 DOM만 접근할 수 있습니다.

또한 ref는 컴포넌트 내부에서만 유효하기때문에 컴포넌트 재사용을 표방하는 리액트에서 ref는 HTML의 id attribute와 달리 중복에 대한 신경을 쓸 필요가 없습니다.