React позволяет легко почти никогда не беспокоиться о базовой объектной модели документа HTML / JavaScript. Рендеринг в сохраненном режиме React позволяет вам определять все на веб-странице декларативно, включая интерактивность и состояние рендеринга. Это великолепно!

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

Вот пример очень простого класса React Component, который использует функцию обратного вызова ref.

Import { Component } from ‘react’;
class ExampleComponent extends Component {
  setFocus() {
    this.textInput.focus();
  }
  render() {
    return (
      <input type=”text”
             ref={(input) => { this.textInput = input; }} />
    );
  }
}

Вот как работает этот пример: когда отображается элемент <input>, React вызывает функцию, определенную в атрибуте ref, передавая этой функции элемент <input> в качестве аргумента.

Это практически тот же пример, который вы найдете в официальных документах React и в большинстве руководств по ссылкам на React. Однако, если вы хотите сделать что-то более сложное, чем просто сохранить ссылку на элемент / компонент, вам не следует использовать этот шаблон.

Мы используем ссылки в кодовой базе Daily.co для управления элементами мультимедиа и холста. Часто нам нужно что-то сделать при первой установке элемента. Итак, примерно так:

class CanvasWithAttitude extends Component {
  setupToDraw(canvas) {
    // ...
  }
render() {
    return (
      <canvas ref={(canvas) => { this.setupToDraw(canvas); }} />
    );
  }
}

За исключением того, что мы этого не делаем, потому что с приведенным выше кодом функция setupToDraw() фактически вызывается дважды для каждого рендеринга. Это определенно не то, что мы хотим.

Каждый раз при рендеринге компонента React необходимо воссоздавать любые встроенные или связанные функции, которые определены как атрибуты элемента. Чтобы удалить старые ссылки, он сначала вызывает встроенную или связанную функцию с аргументом null. Затем он снова вызывает функцию с аргументом элемента / компонента. (Для объяснения этого в официальных документах React прокрутите вниз до самого низа Ссылки и страница DOM.)

Обратите внимание, что я написал «встроенный или связанный», так что это тоже неправильно:

<canvas ref={ this.setupToDraw.bind(this); } />

Наша цель - чтобы функция обратного вызова ref вызывалась один раз при монтировании компонента.

Одно из исправлений - использовать синтаксис свойств класса ES7 для определения функции.

class CanvasWithAttitude extends Component {
  setupToDraw = (canvas) => {
    // ...
  }
  render() {
    return (
      <canvas ref={ this.setupToDraw } />
    );
  }
}

Это создает новую функцию setupToDraw() для каждого экземпляра CanvasWithAttitude и назначает эту функцию свойству setupToDraw объекта. Фактически, мы переместили определение встроенной функции из нашей функции рендеринга и попросили наш транспилятор JavaScript сделать это за нас невидимым волшебством во время создания экземпляра. :-)

У нас есть такой код во многих наших классах, но мне не нравится этот шаблон. В объектной модели JavaScript есть множество угловых случаев, и мне легко забыть, что приведенный выше код делает пару неочевидных вещей. Во-первых, функция setupToDraw() создается для каждого нового экземпляра класса, а во-вторых, функция не существует как часть прототипа класса (поэтому, например, она не будет унаследована).

Вот как я предпочитаю писать этот паттерн.

class CanvasWithAttitude extends Component {
  constructor() {
    super();
    this.setupToDrawBound = this.setupToDraw.bind(this);
  }
  setupToDraw(canvas) {
    // ...
  }
  render() {
    return (
      <canvas ref={ this.setupToDrawBound } />
    );
  }
}

Здесь setupToDrawBound() по-прежнему является новой функцией, которая создается каждый раз при создании экземпляра, но setupToDraw() является нормальной функцией-членом, и такое использование bind() легко понять при сканировании кода. Наконец, и setupToDraw(), и setupToDrawBound наследуются любыми классами, расширяющими CanvasWithAttitude.

Я уверен, что есть и другие шаблоны для создания необычных вещей с помощью React ref. И есть также другие способы структурировать выполнение чего-либо один раз при монтировании компонента. (Мы могли бы, например, подключиться к методу жизненного цикла ReactcomponentDidMount().) Я хотел бы услышать мысли - и особенно примеры интересных вариантов использования - от других разработчиков React!