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

const SomeComponent = forwardRef(render)

Опис

forwardRef(render)

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

import { forwardRef } from 'react';

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

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

Параметри

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

Результат

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

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

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

Функція render

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

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

Ви отримаєте реф як другий аргумент після пропсів. Передайте його у DOM-вузол, який хочете розкрити:

import { forwardRef } from 'react';

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

Це дозволяє батьківському компоненту Form отримати доступ до <input> 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}>
Редагувати
</button>
</form>
);
}

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

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

Приклади направлення рефу

Example 1 of 2:
Фокусування на текстовому полі

Натискання кнопки сфокусує курсор на полі вводу. Компонент 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}>
        Редагувати
      </button>
    </form>
  );
}


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

Замість направлення 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}>
Редагувати
</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}>
        Редагувати
      </button>
    </form>
  );
}


Розкриття імперативного керування замість 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();
    // Це не працюватиме, тому що DOM-вузол не розкритий:
    // ref.current.style.opacity = 0.5;
  }

  return (
    <form>
      <MyInput placeholder="Enter your name" ref={ref} />
      <button type="button" onClick={handleClick}>
        Редагувати
      </button>
    </form>
  );
}

Прочитайте більше про використання імперативного керування.

Pitfall

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

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


Усунення неполадок

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

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

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

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

Щоб виправити це, передайте ref вниз во 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>
);
});