Перенаправлення рефів
These docs are old and won’t be updated. Go to react.dev for the new React docs.
These new documentation pages teach modern React and include live examples:
Перенаправлення рефів — це техніка для автоматичної передачі рефа від компонента до одного із його дітей. Для більшості компонентів, зазвичай, вона не є необхідною. Тим не менше, може бути корисною в деяких випадках, особливо якщо ви пишете бібліотеку. Давайте розглянемо найбільш поширені сценарії.
Перенаправлення рефів у DOM-компоненти
Розглянемо компонент FancyButton
, який рендерить нативний DOM-елемент button
:
function FancyButton(props) {
return (
<button className="FancyButton">
{props.children}
</button>
);
}
React-компоненти приховують деталі своєї реалізації та результат рендеренгу. Також іншим компонентам, які використовують FancyButton
, зазвичай не потрібен доступ до рефа його внутрішнього DOM-елементу button
. І це добре тим, що це запобігає надмірній залежності компонентів від структури DOM-у один одного.
Хоча, така інкапсуляція є бажаною для компонентів, які описують певну закінчену частину додатка, наприклад, FeedStory
або Comment
, це може бути незручним для часто перевикористовуваних “дрібних” компонентів, таких як FancyButton
та MyTextInput
. Ці компоненти використовуються в додатку подібно до звичайних DOM button
чи input
і доступ до їхніх DOM-вузлів може бути необхідним для управління фокусом, виділенням або анімацією.
Перенаправлення рефів дає можливість певному компоненту взяти отриманий реф і передати його далі (іншими словами “перенаправити”) до дочірнього компонента.
У прикладі нижче, FancyButton
використовує React.forwardRef
, щоб отримати переданий йому ref
і перенаправити його в DOM button
, який він рендерить:
const FancyButton = React.forwardRef((props, ref) => ( <button ref={ref} className="FancyButton"> {props.children}
</button>
));
// Тепер ви можете отримати реф беспосередньо на DOM button
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
Таким чином, компоненти, що використовують FancyButton
можуть отримати реф внутрішнього DOM-вузла button
і якщо потрібно, мати доступ до DOM button
подібно до того, якби він використовувався напряму.
Розглянемо цей приклад покроково:
- Ми створюємо React-реф, викликаючи
React.createRef
і записуєм його у зміннуref
. - Ми передаємо наш
ref
у<FancyButton ref={ref}>
, вказуючи його як JSX-атрибут. - React передає
ref
у функцію(props, ref) => ...
всерединіforwardRef
другим аргументом. - Ми перенаправляєм аргумент
ref
далі у<button ref={ref}>
, вказуючи його як JSX-атрибут. - Після прив’язки рефа,
ref.current
буде вказувати на DOM-вузол<button>
.
Примітка
Другий аргумент
ref
існує тільки тоді, коли ви визначаєте компонент як виклик функціїReact.forwardRef
. Звичайні функціональні або класові компоненти не отримуютьref
у якості аргумента чи пропса.Перенаправлення рефів не обмежуються DOM-компонентами. Ви також можете перенаправити реф у екземпляр класового компонента.
Примітка для розробників бібліотек компонентів
Коли ви починаєте використовувати forwardRef
у бібліотеці компонентів, ви повинні вважати це як несумісну зміну і випустити нову мажорну версію. Причина цього в тому, що, швидше за все, компонент буде мати помітно іншу поведінку (наприклад: зміниться тип експортованих даних і елемент, до якого прив’язаний реф), в результаті чого додатки та інші бібліотеки, що покладаються на стару поведінку, перестануть працювати.
З цієї ж причини ми не рекомендуємо викликати React.forwardRef
умовно (тобто перевіряючи чи функція існує). Це змінить поведінку вашої бібліотеки і додатки ваших користувачів можуть перестати працювати при оновленні версії React.
Перенаправлення рефів у компоненти вищого порядку
Ця техніка також може бути особливо корисною у компонентах вищого порядку (ще відомі, як КВП або англ. — HOC). Давайте почнемо з прикладу КВП, який виводить пропси компонента в консоль:
function logProps(WrappedComponent) { class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
return <WrappedComponent {...this.props} />; }
}
return LogProps;
}
КВП “logProps” передає всі props
до компонента, який він обгортає, так що результат рендеру буде такий самий. Наприклад, ми можемо використати цей КВП, щоб вивести усі пропси передані в наш компонент FancyButton
:
class FancyButton extends React.Component {
focus() {
// ...
}
// ...
}
// Замість того, щоб експортувати FancyButton, ми експортуємо LogProps.
// При цьому рендеритись буде FancyButton.
export default logProps(FancyButton);
Щодо прикладу вище є одне застереження: тут рефи не будуть передаватись. Це тому, що ref
не є пропом. React опрацьовує ref
по-іншому, подібно до key
. Якщо ви додасте реф до КВП, реф буде вказувати на зовнішній компонент-контейнер, а не на обгорнутий компонент.
Це означає, що рефи призначені для компонента FancyButton
насправді будуть прив’язані до компонента LogProps
:
import FancyButton from './FancyButton';
const ref = React.createRef();
// Компонент FancyButton, який ми імпортуємо — це КВП LogProps.
// Навіть, якщо результат рендерингу буде таким же самим,
// Наш реф буде вказувати на LogProps, а не на внутрішній компонент FancyButton!
// Це означає, що ми, наприклад, не можемо викликати ref.current.focus()
<FancyButton
label="Click Me"
handleClick={handleClick}
ref={ref}/>;
На щастя, ми можемо явно перенаправити реф до внутрішнього компонента FancyButton
, використовуючи React.forwardRef
API. React.forwardRef
приймає функцію рендеринга, яка отримує параметри props
і ref
та повертає React-вузол. Наприклад:
function logProps(Component) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
const {forwardedRef, ...rest} = this.props;
// Передаємо в якості рефа проп "forwardedRef"
return <Component ref={forwardedRef} {...rest} />; }
}
// Зауважте, другий параметр "ref" переданий від React.forwardRef.
// Ми можемо передати його в LogProps, як звичайний проп, наприклад: "forwardedRef",
// А потім прив'язати його до компоненту.
return React.forwardRef((props, ref) => { return <LogProps {...props} forwardedRef={ref} />; });}
Відображення іншого імені в DevTools
React.forwardRef
отримує фукнцію рендерингу. React DevTools використовує цю функцію, щоб визначити, як відображати компонент перенаправлення рефа.
Наприклад, наступний компонент буде відображатись в DevTools, як ”ForwardRef“:
const WrappedComponent = React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />;
});
Якщо надати ім’я функції рендеринга, то воно з’явиться у назві компонента в DevTools (наприклад, ”ForwardRef(myFunction)”):
const WrappedComponent = React.forwardRef(
function myFunction(props, ref) {
return <LogProps {...props} forwardedRef={ref} />;
}
);
Ви можете навіть додати властивість до функції displayName
і вказати в ній, який саме компонент обгорнутий.
function logProps(Component) {
class LogProps extends React.Component {
// ...
}
function forwardRef(props, ref) {
return <LogProps {...props} forwardedRef={ref} />;
}
// Дамо цьому компоненту більш зрозуміле ім'я в DevTools
// Наприклад, "ForwardRef(logProps(MyComponent))"
const name = Component.displayName || Component.name; forwardRef.displayName = `logProps(${name})`;
return React.forwardRef(forwardRef);
}