3.3 Ссылки ref и DOM

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

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


Существует несколько оправданных случаев использования ссылок ref:

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

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

К примеру, вместо публичных методов open() и close() на компоненте Dialog, передавайте в него свойство isOpen.


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


Внимание!

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


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


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


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


Код
    
  const node = this.myRef.current;
  

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

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

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


3.3.4.1 Добавление ссылки 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() {
      // Явная фокусировка на текстовом поле, используя нативный DOM API
      // Обратите внимание: мы осуществляем доступ к
      // свойству current, чтобы получить DOM-узел
      this.textInput.current.focus();
    }
  
    render() {
      // Мы говорим React, что хотим ассоциировать атрибут ref элемента <input>
      // с `textInput`, который мы создали в конструктооре
      return (
        <div>
          <input
              type="text"
              ref={this.textInput} />
  
          <input
              type="button"
              value="Focus the text input"
              onClick={this.focusTextInput}
          />
        </div>
      );
    }
  }
  

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




3.3.4.2 Добавление ссылки ref на компонент-класс

Если бы мы захотели обернуть компонент 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 {
    // ...
  }
  


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

Нельзя использовать атрибут ref на компонентах-функциях, так как они не имеют экземпляров:


Код
    
  function MyFunctionalComponent() {
    return <input />;
  }
  
  class Parent extends React.Component {
    constructor(props) {
      super(props);
      this.textInput = React.createRef();
    }
    render() {
      // This will *not* work!
      return (
        <MyFunctionalComponent 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="Focus the text input"
            onClick={handleClick}
        />
      </div>
    );
  }
  


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

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

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

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

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


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

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

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


Код
    
  class CustomTextInput extends React.Component {
    constructor(props) {
      super(props);
  
      this.textInput = null;
  
      this.setTextInputRef = element => {
        this.textInput = element;
      };
  
      this.focusTextInput = () => {
        // Фокусировка на текстовом поле, используя нативный DOM API
        if (this.textInput) this.textInput.focus();
      };
    }
  
    componentDidMount() {
      // автофокус на input при монтировании
      this.focusTextInput();
    }
  
    render() {
      // Используйте коллбэк для атрибута `ref`, чтобы сохранить
      // ссылку на текстовый DOM-элемент input в свойстве экземпляра
      // (например this.textInput)
      return (
        <div>
          <input
              type="text"
              ref={this.setTextInputRef}
          />
          <input
              type="button"
              value="Focus the text input"
              onClick={this.focusTextInput}
          />
        </div>
      );
    }
  }
  

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

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


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


Внимание!

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


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