Перенаправлення рефів
Перенаправлення рефів — це техніка для автоматичної передачі рефа від компонента до одного із його дітей. Для більшості компонентів, зазвичай, вона не є необхідною. Тим не менше, може бути корисною в деяких випадках, особливо якщо ви пишете бібліотеку. Давайте розглянемо найбільш поширені сценарії.
Перенаправлення рефів у 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);
}