Unetway

ReactJS - Refs и DOM

Refs предоставляет способ доступа к узлам DOM или элементы React, созданные в методе рендеринга.

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

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

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

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

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

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

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

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

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

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

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

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

const node = this.myRef.current;

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

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

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

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

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

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // create a ref to store the textInput DOM element
    this.textInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
  }

  focusTextInput() {
    // Explicitly focus the text input using the raw DOM API
    // Note: we're accessing "current" to get the DOM node
    this.textInput.current.focus();
  }

  render() {
    // tell React that we want the associate the <input> ref
    // with the `textInput` that we created in the constructor
    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 перехвата жизненного цикла.

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

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

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

Вы не можете использовать 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 must be declared here so the ref can refer to it
  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 из родительского компонента. Обычно это не рекомендуется, так как это разрушает инкапсуляцию компонентов, но иногда может быть полезно для запуска фокуса или измерения размера или положения дочернего узла DOM.

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

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

Это работает как для классов, так и для функциональных компонентов.

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

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

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

Обратите внимание, что имя inputRef реквизита в приведенном выше примере не имеет особого значения, поскольку оно является регулярным компонентом prop. Однако использование refатрибута в <input> самом себе важно, поскольку оно сообщает React о присоединении ref к его узлу DOM.

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

Другим преимуществом этого шаблона является то, что он работает с несколькими компонентами. Например, представьте Parent, что этот узел DOM не нужен, но компонент, который визуализировал Parent(давайте называть его Grandparent), нуждался в доступе к нему. Тогда мы могли бы позволить Grandparentуказать inputRefопору для Parent и переместить Parentее в CustomTextInput:

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

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

class Grandparent extends React.Component {
  constructor(props) {
    super(props);
    this.inputElement = React.createRef();
  }
  render() {
    return (
      <Parent inputRef={this.inputElement} />
    );
  }
}

Здесь ref this.inputElement сначала указывается Grandparent. Он передается Parentв качестве обычного опоры inputRef, и Parent он передает его CustomTextInput в качестве опоры. Наконец, CustomTextInput чтение inputRefprop и прикрепление прошедшего ref как refатрибута к <input>. В результате, this.inputElement.currentв Grandparent будет установлен узел DOM, соответствующий <input> элементу в CustomTextInput.

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

Callback Refs

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

Вместо передачи 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 = () => {
      // Focus the text input using the raw DOM API
      if (this.textInput) this.textInput.focus();
    };
  }

  componentDidMount() {
    // autofocus the input on mount
    this.focusTextInput();
  }

  render() {
    // Use the `ref` callback to store a reference to the text input DOM
    // element in an instance field (for example, 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 протяжении жизненного цикла.

Вы можете передавать обратные вызовы между компонентами, как вы можете, с помощью объектов refs, которые были созданы с помощью 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.

Предостережения с обратными вызовами

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