All'alba vincerò

At dawn, I will win!

React

[React] useRef() : 값을 기억하되 렌더링을 유발하지 않는 Hook

나디아 Nadia 2024. 8. 12. 20:45

📌 useRef

: 컴포넌트의 정보를 기억하되, 해당 정보가 렌더링을 유발하지 않도록 하는 Hook

  • 현재(current) 기억된 값이 변경되더라도 기억하지만, 리-렌더링을 요청하지 않음
    -  state와 달리 ref의 current 값을 설정하면 리렌더가 트리거되지 않는다❌
  • 어떤 데이터를 기억하고 싶은데 리렌더링은 안 하고 싶을 때 사용
    - 화면 업데이트 ❌ 👉 리렌더링을 해야 바뀜
  • 리액트의 것(리액트 훅)이 아닌 것이 아닌 것을 기억해야 할 때 사용
  • DOM 엘리먼트 저장 및 조작, JSX를 계산하는 데 필요하지 않은 다른 객체 저장 등에 사용
  • 자바스크립트 순수 객체 반환
    { current: null } → DOM Mount → { current: HTMLElement }

  • useState 👉 기억된 값을 재렌더링 시 가져온다.
  • useRef 👉 값은 기억하되 렌더링을 유발하지 않는다.

 

 

 

사용

 

1. useRef 불러오기

import { useRef } from 'react';

 

 

2. useRef Hook 사용

    • 참조할 초기값을 유일한 인자로 전달
    • ref.current 프로퍼티를 통해 해당 ref의 current 값에 접근할 수 있다.
      - current 프로퍼티: ref가 참조하는 DOM 요소나 값을 저장
    • useRef(null)인 이유
      - element를 참조할때 기본적으로 null이 들어오기 때문
const ref = useRef(null); 
// return { current: null }

 

 


 

✅ ref로 DOM 조작하기

  • 실제 DOM에 렌더링 된 이후 HTML 요소에 접근 가능
    - 리액트에서 렌더 트리 생성 ➡︎ 리액트 돔에 실제 DOM 생성 ➡︎ ref 콜백 함수 실행 (마운트 시점에서 실행)
  • 참조 콜백(ref callback)
    👉 domElementNode(실제 DOM 요소)를 갖는다
const refCallback = (el /* domElementNode */) => {
   console.log(el);
}

 

 

 

사용

 

1. useRef 불러오기

import { useRef } from 'react';

 

 

2. useRef Hook 사용

const titleRef = useRef(null);

 

 

3. 참조 콜백(ref callback) 또는 이벤트 핸들러(event handler)에 사용

  • 참조 콜백(ref callback)
    👉 domElementNode(실제 DOM 요소)를 갖는다
// 참조 콜백
const refCallback = (el) => {
  if (el) {
    VanillaTilt.init(el, TILT_CONFIG);
  }
};


// 이벤트 핸들러
const handleEnter = () => {
  const title = titleRef.current;
  title.style.opacity = '1';
};

 

 

4. (DOM 노드를 가져와야하는) JSX 태그에 ref 어트리뷰트로 전달

<div ref={refCallback}>

 

 

 

 

ex 1) Vanilla Tilt 이펙트(애니메이션) 적용하기

import VanillaTilt from 'vanilla-tilt';

const TILT_CONFIG = {
  reverse: true,
  max: 15,
  startX: 0,
  startY: 0,
  perspective: 1000,
  scale: 1.2,
  speed: 600,
  transition: true,
  axis: null,
  reset: true,
  'reset-to-start': true,
  easing: 'cubic-bezier(.03,.98,.52,.99)',
  glare: false,
  'max-glare': 0.65,
  'glare-prerender': false,
  'mouse-event-element': null,
  gyroscope: true,
  gyroscopeMinAngleX: -45,
  gyroscopeMaxAngleX: 45,
  gyroscopeMinAngleY: -45,
  gyroscopeMaxAngleY: 45,
};


function CardLinkItem({ item, popup = false, external = false }) {
  const { href, label, images = {} } = item;

  const cardClassNames = `${S.card} ${popup ? S.popup : ''}`.trim();

  let externalLinkProps = null;

  if (external) {
    externalLinkProps = {
      target: '_blank',
      rel: 'noreferrer noopener',
    };
  }


  const titleRef = useRef(null); // { current: null }

  // 참조 콜백(ref callback)
  const refCallback = (el) => {

    if (el) {
      VanillaTilt.init(el, TILT_CONFIG);
    }
  };


  // 이벤트 핸들러
  // - 외부 시스템인 DOM 요소에 접근, 조작
  const handleEnter = () => {
    const title = titleRef.current;
    title.style.opacity = '1';
  };

  const handleLeave = () => {
    titleRef.current.style.opacity = '0';
  };

  return (
    <a
      // key, ref
      key={'identity'}
      ref={refCallback}
      className={S.component}
      aria-label={label}
      title={label}
      href={href}
      onPointerEnter={handleEnter}
      onPointerLeave={handleLeave}
      {...externalLinkProps}
    >
      <figure className={cardClassNames}>
        <div className={S.wrapper}>
          <img className={S.coverImage} src={images.cover} alt="" />
        </div>
        <img ref={titleRef} className={S.title} src={images.title} alt="" />
        <img className={S.character} src={images.character} alt="" />
      </figure>
    </a>
  );
}

 

 


 

 forwardRef()

: 부모 컴포넌트에서 하위 컴포넌트의 DOM을 조작할 수 있도록 하위 컴포넌트에서 부모 컴포넌트의 ref를 받아온다.

const SomeComponent = forwardRef(render)
    • 매개변수
      • render: 컴포넌트의 렌더링 함수
        (React는 컴포넌트가 부모로부터 받은 props와 ref로 이 함수를 호출한다.)
    • 기본적으로 각 컴포넌트의 DOM 노드는 비공개이다.
       forwardRef()로 감싸면 부모에 DOM 노드를 노출할 수 있다

    • ref는 prop으로 전달할 수 없다.
      forwardRef() 사용
      - forwardRef가 반환하는 컴포넌트는부모로부터 ref prop을 받을 수 있다

    • 고차 컴포넌트 사용
      - prop types 사용 불가(➡ TypeScript 사용)
      - 상위 컴포넌트에 공유 불가❌

 

 

 

 사용

 

1. forwardRef 불러오기

import { forwardRef } from 'react';

 

 

2. ref를 받아서 사용할 렌더 컴포넌트 감싸기

  • 해당 컴포넌트의 props으로 ref 전달 & 사용
  • 컴포넌트가 부모로부터 받은 props와 ref로 해당 함수를 호출
const SomeComponent = forwardRef(function MyInput(props, ref) {

  return (
    // ...
  )
});

 

 

3. 노출하려는 하위 컴포넌트의 DOM 노드에 ref 어트리뷰트 설정하기 

const MyInput = forwardRef(function MyInput(props, ref) {

  return (
     <input {...otherProps} ref={ref} />
  )
});

 

 

 

 부모 컴포넌트(Form)가 자식 컴포넌트(MyInput)의 <input> DOM 노드에 접근할 수 있다.

function Form() {
  const ref = useRef(null);

  function handleClick() {
    ref.current.focus();
  }

  return (
    <form>
      <MyInput label="Enter your name:" ref={ref} />
    </form>
  );
}

 

 


 

✅ useImperativeHandle()

: 하위 컴포넌트에서 ref로 노출할 이벤트 핸들러를 정의한다. 

useImperativeHandle(ref, createHandle, dependencies?)
  • 매개변수
    • ref: forwardRef 렌더링 함수에서 두 번째 인자로 받은 ref
    • createHandle: 노출하려는 ref 핸들을 반환하는 함수(노출하려는 메서드가 있는 객체를 반환)
    • dependencies: createHandle 코드 내에서 참조하는 모든 반응형 값을 나열한 목록입
      - 반응형 값: props, state 및 컴포넌트 내에서 직접 선언한 모든 변수와 함수
  • 부모 컴포넌트가 자식 컴포넌트의 특정 메서드나 속성에 접근할 수 있다
  • 주로 forwardRef와 함께 사용

 

 

 

 사용

 

1. useImperativeHandle 불러오기

import { forwardRef, useImperativeHandle } from 'react';

 

 

2. forwardRef 함수 내부에 useImperativeHandle 사용

const MyInput = forwardRef(function MyInput(props, ref) {

  useImperativeHandle(ref, () => {
    return {
      // 메서드
    };
  }, []);
}

 

 

 

  부모 컴포넌트(Form)가 자식 컴포넌트(MyInput)의 focusscrollIntoView 메서드를 호출할 수 있다.

(기본 <input> DOM 노드의 전체 엑세스 권한은 없음)

import { forwardRef, useRef, useImperativeHandle } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () => {
    return {
      focus() {
        inputRef.current.focus();
      },
      scrollIntoView() {
        inputRef.current.scrollIntoView();
      },
    };
  }, []);

  return <input {...props} ref={inputRef} />;
});

 

 


📢 함수 내에서 데이터 기억하는 방법

* 함수 내 지역 변수(함수 실행 이후엔 기억되지 않는다.)


1. useState()

  • 메모이제이션, 외부에 데이터 기억 저장/읽기
  • 필수적으로 리액트 컴포넌트를 리-렌더링 하도록 요청⭕
    👉 값을 기억하기 위해 사용하는 것은 낭비
  • 화면을 업데이트(변경) 해주는 데이터
    👉 리액트의 것(리액트 훅)을 기억하고, 컴포넌트(함수)를 다시 실행시킬 때 사용
  • Immutable(불변)
let [message, setMessage] = useState(''); // return [state, setState]

 

 

 

2. useRef()

  • 메모이제이션, 외부에 데이터 기억 저장/읽기
  • 현재(current) 기억된 값이 변경되더라도 기억은 하지만, 리-렌더링 하도록 요청하지 않음❌
  • 어떤 데이터를 기억하고 싶은데 리렌더링은 안 하고 싶을 때
    (화면 업데이트 X, 리렌더링을 해야 바뀜)
    👉 리액트의 것(리액트 훅)이 아닌 것이 아닌 것을 기억해야 할 때 사용
  • Mutable(변경 O)
const messageRef = useRef(''); 
// return { current: '' }
// 일반 js 객체가 반환, current에 초기값을 할당

 

 

 

 

 

☑️ useRef  vs   useState

useRef(initialValue)는 
{ current: initialValue } 반환
useState(initialValue)은
[value, setValue] 반환
state를 바꿔도 리렌더링❌ state를 바꾸면 리렌더링⭕
Mutable
-렌더링 프로세스 외부에서 
current 값을 수정 및 업데이트할 수 있다.
Immutable
- state 를 수정하기 위해서는
state 설정 함수를 통해 리렌더 대기열에 넣어야 한다.
렌더링 중에는 current 값 수정 언제든지 state를 읽을 수 있다
그러나 각 렌더마다 변경되지 않는 자체적인 state의 snapshot이 있다.

 

 


 

 

Ref로 값 참조하기 – React

The library for web and native user interfaces

ko.react.dev

 

 

Ref로 DOM 조작하기 – React

The library for web and native user interfaces

ko.react.dev

 

 

공통 컴포넌트 (예시: <div>) – React

The library for web and native user interfaces

ko.react.dev

 

 

forwardRef – React

The library for web and native user interfaces

ko.react.dev

 

 

useImperativeHandle – React

The library for web and native user interfaces

ko.react.dev