forwardRef

forwardRef дозволяє вашому компоненту показувати вузол DOM батьківському компоненту за допомогою посилання.

const SomeComponent = forwardRef(render)

Довідник

forwardRef(render)

Викличте forwardRef(), щоб ваш компонент отримав посилання і передав його дочірньому компоненту:

import { forwardRef } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  // ...
});

Дивіться більше прикладів нижче.

Параметри

  • render: Функція рендерингу для вашого компонента. React викликає цю функцію з пропсами та рефом, які ваш компонент отримав від свого батька. JSX, який ви повернете, буде виходом вашого компонента.

Повернення

forwardRef повертає React-компонент, який ви можете відрендерити в JSX. На відміну від React-компонентів, визначених як прості функції, компонент, що повертається forwardRef, може приймати проп ref.

Застереження

  • У суворому режимі React викличе вашу функцію рендерингу двічі, щоб допомогти вам знайти випадкові домішки. Це поведінка лише для розробки і не впливає на виробництво. Якщо ваша функція рендерингу чиста (як і має бути), це не повинно вплинути на логіку роботи вашого компонента. Результат одного з викликів буде проігноровано.

відрендерити функцію

forwardRef приймає функцію рендерингу як аргумент. React викликає цю функцію за допомогою пропсів та рефа:

const MyInput = forwardRef(function MyInput(props, ref) {
  return (
    <label>
      {props.label}
      <input ref={ref} />
    </label>
  );
});

Параметри

  • props: Пропси, передані батьківським компонентом.

  • ref: Атрибут ref передано батьківським компонентом. ref може бути об'єктом або функцією. Якщо батьківський компонент не передав посилання, це буде null. Ви повинні або передати отримане ref іншому компоненту, або передати його в useImperativeHandle.

Повернення

forwardRef повертає React-компонент, який ви можете відрендерити в JSX. На відміну від React-компонентів, визначених як прості функції, компонент, що повертається forwardRef, може приймати проп ref.


Використання

Відкриття DOM-вузла батьківському компоненту

За замовчуванням, вузли DOM кожного компонента є приватними. Однак іноді корисно зробити DOM-вузол доступним для батьківського компонента - наприклад, щоб дозволити його фокусування. Щоб скористатися цією можливістю, оберніть визначення вашого компонента у forwardRef():

import { forwardRef } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  const { label, ...otherProps } = props;
  return (
    <label>
      {label}
      <input {...otherProps} />
    </label>
  );
});

Ви отримаєте посилання </CodeStep> як другий аргумент після пропсів. Передайте його до DOM-вузла, який ви хочете показати:</p> <pre><code data-meta="{8} [[1, 3, "ref"], [1, 8, "ref", 30]]" class="language-js">import { forwardRef } from 'react'; const MyInput = forwardRef(function MyInput(props, ref) { const { label, ...otherProps } = props; return ( <мітка>; {label} <input {...otherProps} ref={ref} /> </label> ); });

Це дозволяє батьківському компоненту Form отримати доступ до <коду><введення> DOM-вузла. викрито MyInput:

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

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

  return (
    <form>
      <MyInput label="Enter your name:" ref={ref} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}

Цей Form компонент передає посилання на MyInput. Компонент MyInput передає , який посилається на тег браузера <input>. У результаті компонент Form може отримати доступ до цього вузла DOM <input> і викликати на ньому focus().

Майте на увазі, що виставляння рефа на DOM-вузол всередині вашого компонента ускладнює подальшу зміну внутрішньої структури вашого компонента. Зазвичай ви показуєте DOM-вузли для повторно використовуваних низькорівневих компонентів, таких як кнопки або текстові поля, але не варто цього робити для компонентів рівня застосунку, таких як аватар або коментар.

Фокусування текстового введення

Натискання кнопки сфокусує введення. Компонент Form визначає посилання і передає його компоненту MyInput. Компонент MyInput пересилає це посилання браузеру <input>. Це дозволяє компоненту Form сфокусувати <input>.

import { useRef } from 'react';
import MyInput from './MyInput.js';

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

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

  return (
    <form>
      <MyInput label="Enter your name:" ref={ref} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}
import { forwardRef } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  const { label, ...otherProps } = props;
  return (
    <label>
      {label}
      <input {...otherProps} ref={ref} />
    </label>
  );
});

export default MyInput;
input {
  margin: 5px;
}

Відтворення та призупинення відео

Натискання кнопки викличе play() та pause() на DOM-вузлі <video>. Компонент App визначає посилання і передає його компоненту MyVideoPlayer. Компонент MyVideoPlayer пересилає це посилання до вузла браузера <video>. Це дозволяє компоненту App відтворювати та ставити на паузу <video>.

import { useRef } from 'react';
import MyVideoPlayer from './MyVideoPlayer.js';

export default function App() {
  const ref = useRef(null);
  return (
    <>
      <button onClick={() => ref.current.play()}>
        Play
      </button>
      <button onClick={() => ref.current.pause()}>
        Pause
      </button>
      <br />
      <MyVideoPlayer
        ref={ref}
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
        type="video/mp4"
        width="250"
      />
    </>
  );
}
import { forwardRef } from 'react';

const VideoPlayer = forwardRef(function VideoPlayer({ src, type, width }, ref) {
  return (
    <video width={width} ref={ref}>
      <source
        src={src}
        type={type}
      />
    </video>
  );
});

export default VideoPlayer;
button { margin-bottom: 10px; margin-right: 10px; }

Пересилання рефа через декілька компонентів

Замість того, щоб перенаправляти ref на вузол DOM, ви можете перенаправити його на власний компонент як MyInput:

const FormField = forwardRef(function FormField(props, ref) {
  // ...
  return (
    <>
      <MyInput ref={ref} />
      ...
    </>
  );
});

Якщо компонент MyInput пересилає посилання на свій <input>, то посилання на FormField дасть вам таке <input>:

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

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

  return (
    <form>
      <FormField label="Enter your name:" ref={ref} isRequired={true} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}

Компонент Form визначає реф і передає його до FormField. Компонент FormField пересилає це посилання до MyInput, який пересилає його до DOM-вузла браузера <input>. Ось як Form отримує доступ до цього DOM-вузла.

import { useRef } from 'react';
import FormField from './FormField.js';

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

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

  return (
    <form>
      <FormField label="Enter your name:" ref={ref} isRequired={true} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}
import { forwardRef, useState } from 'react';
import MyInput from './MyInput.js';

const FormField = forwardRef(function FormField({ label, isRequired }, ref) {
  const [value, setValue] = useState('');
  return (
    <>
      <MyInput
        ref={ref}
        label={label}
        value={value}
        onChange={e => setValue(e.target.value)} 
      />
      {(isRequired && value === '') &&
        <i>Required</i>
      }
    </>
  );
});

export default FormField;
import { forwardRef } from 'react';

const MyInput = forwardRef((props, ref) => {
  const { label, ...otherProps } = props;
  return (
    <label>
      {label}
      <input {...otherProps} ref={ref} />
    </label>
  );
});

export default MyInput;
input, button {
  margin: 5px;
}

Виставляння імперативного дескриптора замість вузла DOM

Замість того, щоб показувати весь вузол DOM, ви можете показати користувацький об'єкт, який називається імперативний дескриптор, з більш обмеженим набором методів. Для цього вам потрібно визначити окреме посилання для вузла DOM:

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

  // ...

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

Передайте отриманий вами ref до useImperativeHandle і вкажіть значення, яке потрібно виставити до ref:

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} />;
});

Якщо якийсь компонент отримує посилання на MyInput, він отримає лише ваш об'єкт { focus, scrollIntoView } замість вузла DOM. Це дозволяє вам обмежити інформацію, яку ви розкриваєте про DOM-вузол, до мінімуму.

import { useRef } from 'react';
import MyInput from './MyInput.js';

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

  function handleClick() {
    ref.current.focus();
    // This won't work because the DOM node isn't exposed:
    // ref.current.style.opacity = 0.5;
  }

  return (
    <form>
      <MyInput placeholder="Enter your name" ref={ref} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}
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} />;
});

export default MyInput;
input {
  margin: 5px;
}

Довідка про використання імперативних ручок.

Не зловживайте рефами. Ви повинні використовувати рефи лише для імперативної поведінки, яку ви не можете виразити пропсами: наприклад, прокрутка до вузла, фокусування вузла, запуск анімації, виділення тексту і так далі.

Якщо ви можете виразити щось як проп, не варто використовувати реф. Наприклад, замість того, щоб виставляти імперативний дескриптор типу { open, close } з компонента Modal, краще взяти isOpen як проп типу <Modal isOpen={isOpen} />. Ефекти можуть допомогти вам розкрити імперативну поведінку за допомогою пропсів.


Налагодження

Мій компонент обгорнутий в forwardRef, але посилання на нього завжди null

.

Зазвичай це означає, що ви забули використати отримане вами посилання.

Наприклад, цей компонент нічого не робить зі своїм посиланням:

const MyInput = forwardRef(function MyInput({ label }, ref) {
  return (
    <label>
      {label}
      <input />
    </label>
  );
});

Щоб виправити це, передайте посилання вниз до вузла DOM або іншого компонента, який може приймати посилання:

const MyInput = forwardRef(function MyInput({ label }, ref) {
  return (
    <label>
      {label}
      <input ref={ref} />
    </label>
  );
});

Посилання ref на MyInput також може бути null, якщо частина логіки є умовною:

const MyInput = forwardRef(function MyInput({ label, showInput }, ref) {
  return (
    <label>
      {label}
      {showInput && <input ref={ref} />}
    </label>
  );
});

Якщо showInput є false, то посилання не буде передано до жодного вузла, а посилання на MyInput залишиться порожнім. Це особливо легко не помітити, якщо умова прихована всередині іншого компонента, як Panel у цьому прикладі:

const MyInput = forwardRef(function MyInput({ label, showInput }, ref) {
  return (
    <label>
      {label}
      <Panel isExpanded={showInput}>
        <input ref={ref} />
      </Panel>
    </label>
  );
});