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