🎉 Читай официальный перевод документации! ➡️ ru.reactjs.org 🎉 (⭐ ...и не забудь поставить «звёздочку» репозиторию! ⭐)

Refs и DOM

Refs (далее просто «ссылки») предоставляет способ доступа к DOM-узлам или React-элементам, созданным в методе render().

В обычном потоке данных React свойства — единственный способ взаимодействия родительских компонентов со своими дочерними элементами. Чтобы изменить дочерний элемент, вы повторно отрисовываете его с помощью новых свойств. Тем не менее, есть несколько случаев, когда вам необходимо принудительно модифицировать дочерний элемент за пределами типичного потока данных. Изменяемый дочерний элемент может быть экземпляром компонента React или DOM-элементом. Для обоих этих случаев React предоставляет запасной вариант.

Когда использовать ссылки

Есть несколько хороших примеров использования ссылок:

  • Управление фокусом, выделение текста или воспроизведение медиаресурсами.
  • Выполнение анимаций в императивном подходе.
  • Интеграция со сторонними библиотеками, взаимодействующие с DOM.

Избегайте использования ссылок для всего, что может быть сделано в декларативном стиле.

Например, вместо использования методов open() и close() в компоненте Dialog, передайте ему свойство isOpen.

Не злоупотребляйте использованием ссылок

Вашим первым побуждением для использования ссылок может быть в том, «чтобы они заработали» в вашем приложении. Если это так, остановитесь и подумайте о том, кому должно принадлежать состояние в иерархии компонентов. Часто становится ясно, что правильное место для «владения» этим состоянием находится на более высоком уровне в иерархии. Смотрите руководство по поднятию состояния для в качестве примера.

Примечание

Приведённые ниже примеры были обновлены, чтобы использовать API React.createRef(), введённый в React 16.3. Если вы используете более раннюю версию React, мы рекомендуем использовать ссылки-колбэки.

Создание ссылок

Ссылки создаются с использованием React.createRef() и добавляются к React-элементам с помощью атрибута ref. Ссылки обычно присваиваются свойству экземпляра, когда компонент создаётся таким образом, чтобы на них можно было ссылаться по всему компоненту.

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}

Доступ к ссылкам

Когда ссылка передаётся элементу в render(), доступ к её узлу можно получить в атрибуте current.

const node = this.myRef.current;

Значение ссылки отличается в зависимости от типа узла:

  • Когда атрибут ref используется в HTML-элементе, ref, созданный в конструкторе с помощью React.createRef(), получает базовый элемент DOM в качестве своего свойства current.
  • Когда атрибут ref используется на пользовательском классовом компоненте, объектref получает примонтированный экземпляр компонента в качестве своего свойства current.
  • Вы не можете использовать атрибут ref в функциональных компонентах, потому что у них не может быть экземпляров.

Приведённые ниже примеры демонстрируют данные различия.

Добавление ссылки на DOM-элемент

Этот код использует ref для сохранения ссылки на узел DOM:

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // Создание ссылки для сохранения DOM-элемента textInput
    this.textInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
  }

  focusTextInput() {
    // Явная установка фокуса на поле ввода текста с использованием непосредственно API DOM
    // Примечание: мы получаем доступ к "current" для получения DOM-узла
    this.textInput.current.focus();
  }

  render() {
    // Указать React, что мы хотим связать ссылку <input>
    // со свойством `textInput`, созданного в конструкторе
    return (
      <div>
        <input
          type="text"
          ref={this.textInput} />
        <input
          type="button"
          value="Установить фокус на поле ввода текста"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

При монтировании компонента React присваивает свойству current элемент DOM, и назначить ему обратно значение null при размонтировании компонента. Обновления ref происходят перед выполнением хук жизненного цикла componentDidMount или componentDidUpdate.

Добавление ссылки на классовый компонент

Если бы мы хотели обернуть показанный выше CustomTextInput для имитации щелчка по нему сразу после монтирования, мы могли бы использовать атрибут ref для доступа к пользовательскому полю ввода и вызвать вручную метод focusTextInput:

class AutoFocusTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }

  componentDidMount() {
    this.textInput.current.focusTextInput();
  }

  render() {
    return (
      <CustomTextInput ref={this.textInput} />
    );
  }
}

Обратите внимание, что это работает только в том случае, если CustomTextInput объявлен как класс:

class CustomTextInput extends React.Component {
  // ...
}

Ссылки и функциональные компоненты

Вы не можете использовать атрибут ref на функциональных компонентах, потому что из них нельзя создать экземпляры:

function MyFunctionComponent() {
  return <input />;
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }
  render() {
    // Это *не* будет работать!
    return (
      <MyFunctionComponent ref={this.textInput} />
    );
  }
}

Вам нужно преобразовать функциональный компонент в классовый, если вам нужна ссылка на него, по аналогии с тем, когда вам нужны методы жизненного цикла или состояние.

Однако вы можете использовать атрибут ref внутри функционального компонента, если вы ссылаетесь на DOM-элемент или классовый компонент:

function CustomTextInput(props) {
  // textInput должен быть объявлен здесь, чтобы атрибут ref мог сослаться на него
  let textInput = React.createRef();

  function handleClick() {
    textInput.current.focus();
  }

  return (
    <div>
      <input
        type="text"
        ref={textInput} />
      <input
        type="button"
        value="Установка фокуса на поле ввода текста"
        onClick={handleClick}
      />
    </div>
  );
}

Предоставление DOM-ссылок на родительские компоненты

В редких случаях вам может потребоваться доступ к дочернему узлу DOM из родительского компонента. Обычно это не рекомендуется, так как это нарушает инкапсуляцию компонентов, но иногда может быть полезно для установки фокуса, определения размера элемента или положения дочернего DOM-узла.

Хотя вы можете добавить ссылку на дочерний компонент, это не идеальное решение, так как вы получите только экземпляр компонента, а не узел DOM. Кроме того, это не будет работать с функциональными компонентами.

Если вы используете React 16.3 или выше, мы рекомендуем использовать передачу ссылок для подобных случаев. Передача ссылок позволяет компонентам предоставлять ссылки любым дочерним компонентам. Вы можете найти подробный пример того, как передать дочерний DOM-узел родительскому компоненту в документации по передаче ссылок.

Если вы используете React 16.2 или ниже, или если вам требуется больше гибкости, чем предоставляется передачей ссылок, вы можете использовать такой альтернативный подход и явно передать ссылку в виде свойства с другим именем.

Когда это возможно, мы советуем не предоставлять доступ к узлам DOM, но это может быть полезным запасным вариантом. Обратите внимание, что для этого подхода вам необходимо добавить немного кода к дочернему компоненту. Если у вас нет абсолютно никакого контроля над реализацией дочернего компонента, последний вариант — использовать findDOMNode(), но это не приветствуется и устарело в StrictMode.

Ссылки-колбэки

React также поддерживает другой способ установки ссылок, называемый «ссылки-колбэки», который предоставляет более точное управление ссылками, когда они установлены и не установлены.

Вместо передачи атрибута ref, созданногоcreateRef(), вы передаёте функцию. Функция получает экземпляр компонента React или DOM-элемент HTML в качестве своего аргумента, который можно сохранить и получить в другом месте.

В приведённом ниже примере показано обычный паттерн: использование колбэка ref для сохранения ссылки на узел DOM в свойстве экземпляра.

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);

    this.textInput = null;

    this.setTextInputRef = element => {
      this.textInput = element;
    };

    this.focusTextInput = () => {
      // Установка фокуса на поле ввода текста, используя непосредственно API DOM
      if (this.textInput) this.textInput.focus();
    };
  }

  componentDidMount() {
    // Автоматическая установка фокуса на поле ввода текста при монтировании
    this.focusTextInput();
  }

  render() {
    // Использование колбэка на `ref` для сохранения ссылки на DOM-элемента поля ввода текста
    // в свойстве экземпляра (например, this.textInput).
    return (
      <div>
        <input
          type="text"
          ref={this.setTextInputRef}
        />
        <input
          type="button"
          value="Установка фокуса на поле ввода текста"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

React будет вызывать колбэк, переданный в атрибуте ref с DOM-элементом при монтировании компонента и вызовет его со значением null при размонтировании. Колбэки в атрибуте ref вызывается перед хуками жизненного цикла componentDidMount или componentDidUpdate.

Вы можете передавать колбэки между компонентами, также как это возможно с объектом ссылок, создаваемых с помощью React.createRef().

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {
  render() {
    return (
      <CustomTextInput
        inputRef={el => this.inputElement = el}
      />
    );
  }
}

В приведённом выше примере Parent передаёт свой колбэк-ссылку в качестве свойства inputRef в CustomTextInput, а CustomTextInput передаёт эту же функцию целевому атрибуту ref элемента <input>. В результате this.inputElement в Parent будет установлен на узел DOM, соответствующий элементу <input> в CustomTextInput.

Устаревшее API: ссылки в виде строк

Если вы раньше работали с React, вы можете ознакомиться с более старым API, где атрибут ref — это строка, например "textInput", а к DOM-узлу доступ можно получить используя this.refs.textInput. Мы не советуем использовать этот вариант, потому что у ссылок в виде строк есть ряд проблем, они считаются устаревшими и скорее всего будут удалены в одном из будущем релизе.

Примечание

Если вы используете this.refs.textInput для доступа к ссылками, мы рекомендуем использовать вместо этого ссылки-колбэки) или API createRef.

Предостережения по поводу ссылок-колбэков

Если колбэк ref определён как встроенная функция, он будет вызываться дважды во время обновлений, первый раз со значением null, а затем снова с DOM-элементом. Это связано с тем, что с каждой отрисовкой создаётся новый экземпляр функции, поэтому React необходимо очистить старую ссылку и настроить новую. Этого можно избежать, указав колбэк ref как привязанный метод к классу, но обратите внимание, что в большинстве случаев это не имеет значения.