We want to hear from you!Take our 2021 Community Survey!
This site is no longer updated.Go to react.dev

Перенаправлення рефів

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 подібно до того, якби він використовувався напряму.

Розглянемо цей приклад покроково:

  1. Ми створюємо React-реф, викликаючи React.createRef і записуєм його у змінну ref.
  2. Ми передаємо наш ref у <FancyButton ref={ref}>, вказуючи його як JSX-атрибут.
  3. React передає ref у функцію (props, ref) => ... всередині forwardRef другим аргументом.
  4. Ми перенаправляєм аргумент ref далі у <button ref={ref}>, вказуючи його як JSX-атрибут.
  5. Після прив’язки рефа, 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);
}