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>
);
});
Ви отримаєте Це дозволяє батьківському компоненту Цей Майте на увазі, що виставляння рефа на DOM-вузол всередині вашого компонента ускладнює подальшу зміну внутрішньої структури вашого компонента. Зазвичай ви показуєте DOM-вузли для повторно використовуваних низькорівневих компонентів, таких як кнопки або текстові поля, але не варто цього робити для компонентів рівня застосунку, таких як аватар або коментар.Form
отримати доступ до 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()
.
Фокусування текстового введення
Натискання кнопки сфокусує введення. Компонент 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>
);
});