Ref-атрибуты и DOM в React

https://facebook.github.io/react/docs/refs-and-the-dom.html
  • Перевод

React. Продвинутые руководства. Часть Третья


Продолжение серии переводов раздела "Продвинутые руководства" (Advanced Guides) официальной документации библиотеки React.js.


Ref-атрибуты и DOM в React


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


Атрибут обратного вызова ref


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


Когда атрибут ref используется в элементе HTML, функция обратного вызова принимает базовый элемент DOM в качестве аргумента. Например, следующий код использует функцию обратного вызова, указанную в ref, для сохранения ссылки на узел DOM:


class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.focus = this.focus.bind(this);
  }

  focus() {
    // Установка фокуса на поле текстового ввода (input) с явным использованием исходного API DOM
    this.textInput.focus();
  }

  render() {
    // Использование обратного вызова `ref` для сохранения ссылки на поле текстового ввода (input)
    // как элемента DOM в this.textInput.
    return (
      <div>
        <input
          type="text"
          ref={(input) => { this.textInput = input; }} />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focus}
        />
      </div>
    );
  }
}

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


Использование обратного вызова ref для установки свойства в классе — это общепринятый шаблон для доступа к элементам DOM. Предпочтительный способ для установки свойства с использованием обратного вызова ref — тот который приведен в примере выше. Есть еще более короткий способ для реализации этого: ref={input => this.textInput = input}.


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


Когда атрибут ref используется в кастомном компоненте React, функция обратного вызова принимает смонтированный экземпляр компонента в качестве аргумента. Например, если мы захотели обернуть input из предыдущего примера в компонент CustomTextInput для симуляции клика сразу после монтирования:


class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.focus = this.focus.bind(this);
  }

  focus() {
    // Установка фокуса на поле текстового ввода (input) с явным использованием исходного API DOM
    this.textInput.focus();
  }

  render() {
    // Использование обратного вызова `ref` для сохранения ссылки на поле текстового ввода (input)
    // как элемента DOM в this.textInput.
    return (
      <div>
        <input
          type="text"
          ref={(input) => { this.textInput = input; }} />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focus}
        />
      </div>
    );
  }
}

class AutoFocusTextInput extends React.Component {
  componentDidMount() {
    this.textInput.focus();
  }

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

Нельзя использовать атрибут ref с компонентом, построенным на функции (stateless компонент), т.к. функция не имеет экземпляров. Однако, вы можете использовать атрибут ref внутри такого компонента:


function CustomTextInput(props) {
  // textInput задекларирован здесь, т.к. обратный вызов ref ссылается на него
  let textInput = null;

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

  return (
    <div>
      <input
        type="text"
        ref={(input) => { textInput = input; }} />
      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );  
}

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


Вашей первой мыслью может быть — что использование ref "превратит мечту в реальность" в вашем приложении. Если это так, то остановитесь и критически подумайте — верно ли расположены состояния в вашей иерархии компонентов. Часто возникает такая ситуация, что перерасположение состояния выше в иерархии компонентов, чем оно находится в настоящий момент, решает проблему. Смотрите руководство Подъем Состояния выше как пример этого.


Следующие части:



Предыдущие части:



Первоисточник: React — Advanced Guides — Refs and the DOM

Поделиться публикацией
Похожие публикации
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 24
  • +3
    Как альтернатива — можно использовать строковое название и тогда не придется каждый вызов создавать анонимную функцию, не знаю, почему в статье нету этого примера.

    class Hello extends React.Component {
      constructor() {
        this.focus = this.focus.bind(this);
      }
    
      handleClick() {
        this.refs.myTextInput.focus();
      }
    
      render() {
        return (
          <div>
            <input type="text" ref="myTextInput" />
            <input type="button" value="Focus" onClick={this.focus} />
          </div>
        );
      }
    }
    


    edit: таки описано:
    Использование обратного вызова ref для установки свойства в классе — это общепринятый шаблон для доступа к элементам DOM. Если в настоящий момент для этой задачи вы используете this.refs.myRefName, мы рекомендуем перейти к использованию описанного нами шаблона.


    не хватает аргументов, почему рекомендуют.
    • 0
      В статье об этом написано:
      Использование обратного вызова ref для установки свойства в классе — это общепринятый шаблон для доступа к элементам DOM. Если в настоящий момент для этой задачи вы используете this.refs.myRefName, мы рекомендуем перейти к использованию описанного нами шаблона.
      • +1
        Не надо давать уникальные имена универсальным шаблонным элементам, можно использовать универсальные функции. Представьте, что у вас 2 inputa и 2 кнопки. И там и там одинаковый функционал.
        • 0
          Звучит как место для вывода кода в отдельный компонент. Но я не могу понять, что именно вы предлагаете.
          • 0
            Как различать какие элементы демонтируются?
          • +1
            не хватает аргументов, почему рекомендуют.

            У меня сложилось впечатление, что это для того, чтобы люди избегали использования ref как таковых, окромя тех ситуаций, когда иначе никак. Может быть с их точки зрения, люди злоупотребляют этой возможностью в ущерб react-way.

            • +2
              Тогда надо сделать depricated, а потом убрать )
              • +1
                Да, если добавить сюда ранее не документированный контекст, dangerouslySetInnerHTML={{ __html: «Hello» }}, вечные предупреждения при управлении div[contenteditable=«true»] — то получится сплошная политика запретов. VUE в этом плане куда свободнее
                • 0
                  У меня сложилось впечатление, что это для того, чтобы люди избегали использования ref как таковых, окромя тех ситуаций, когда иначе никак.

                  И молодцы. Дали теперь возможность таким людям писать что-то вроде такого:

                  <input ref={(input) => calculateSomething()} />
                  


                  Архитекторы очень мудры
                • 0

                  Вопрос к эксперту: а разве такого рода даже анонимные функции не успешно ли оптимизируются движком и фактически функция заново не создается? Или все же она сразу же выбрасывается из памяти после выполнения render?

                  • +2
                    Если только с точки зрения теории, то стоит не забывать, что это не просто блок кода, а объект первого класса, он создается, к нему цепляется scope и prototype, его присваивают переменной. Чистые функции легче оптимизировать (там нет необходимости оставлять замыкание), эта функция грязная.

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

                    Более того, кроме чисто js-нюансов есть и важная особенность Реакта. Процитирую из документации:
                    class LoggingButton extends React.Component {
                      handleClick() {
                        console.log('this is:', this);
                      }
                    
                      render() {
                        // This syntax ensures `this` is bound within handleClick
                        return (
                          <button onClick={(e) => this.handleClick(e)}>
                            Click me
                          </button>
                        );
                      }
                    }
                    


                    The problem with this syntax is that a different callback is created each time the LoggingButton renders. In most cases, this is fine. However, if this callback is passed as a prop to lower components, those components might do an extra re-rendering. We generally recommend binding in the constructor or using the property initializer syntax, to avoid this sort of performance problem.


                    То есть каждую перерисовку LoggingButton внутренний button будет получать НОВЫЙ экземпляр функции и потому Реакт будет считать, что его необходимо обновить, хотя можно было бы использовать закешированный (старый) вариант, если бы экземпляр функции с контекстом был создан и сохранен изначально.

                    пс. Есть очень хорошая статья, которая поможет понять, что происходит и какая работа выполняется при создании анонимной функции: http://dmitrysoshnikov.com/ecmascript/ru-chapter-6-closures/
                    • +2
                      То есть каждую перерисовку LoggingButton внутренний button будет получать НОВЫЙ экземпляр функции и потому Реакт будет считать, что его необходимо обновить, хотя можно было бы использовать закешированный (старый) вариант, если бы экземпляр функции с контекстом был создан и сохранен изначально.


                      Следует понимать что это актуально только если реализовывается shouldComponentUpdate со сравнением функций переданных через props. Стандартная реализация shouldComponentUpdate просто возвращает true, поэтому компоненту глубоко плевать на то, новосозданная это функция или закешированная.

                      П.С. вот хорошая статья на тему производительности реакта http://blog.csssr.ru/2016/12/07/react-perfomance/
                      • 0
                        Или если что-то типа Redux/MobX/… подставляет свою функцию, о чём можно даже не догадываться при поверхностном изучении. Второй вариант — несколько этих компонентов на странице, в такой форме будет создаваться новая функция для каждого элемента, а могла бы одна биндиться.
                  • +1
                    Вот тут участники реакта немного проясняют свою позицию:
                    There are multiple problems with it:

                    • It requires that React keeps track of currently rendering component (since it can't guess this). This makes React a bit slower.
                    • It doesn't work as most people would expect with the «render callback» pattern (e.g.
                      <DataGrid renderRow={this.renderRow} />
                      
                      ) because the ref would get placed on DataGrid for the above reason.
                    • It is not composable, i.e. if a library puts a ref on the passed child, the user can't put another ref on it (e.g. #8734). Callback refs are perfectly composable.


                    • +1
                      Кстати, эту информацию уже добавили в официальную доку, неплохо бы наверное и перевод обновить ;-)
                • –3
                  Опять невалидное. Почему бы им просто не использовать data-атрибуты. Каждый пытается свою спецификацию сделать…
                  • 0

                    Дык, эти атрибуты же не добираются до DOM. Это внутренняя кухня.

                    • –2
                      Если честно просто не пользуюсь реактом(и ангуляром из-за несоблюдения стандартов), но в любом случае этих атрибутом нет в спецификации. Есть data-атрибуты. Вы хотите сказать, что в выхлопе на HTML они урезаются чтоли или переформируются в валидный код?
                      • +4
                        В Реакте не используется HTML, JSX (JS+XML) не надмножество ни HTML, ни даже xHTML. Как говорится, все элементы и атрибуты XML вымышлены, все совпадения с HTML случайны. JSX-элементы «рендерятся» в вызовы JS-функций CreateElement, которые возвращают элементы виртуального DOM, которые потом «рендерятся» в реальный. HTML в процессе вообще не участвует (серверный рендеринг опустим для простоты), а алгоритм маппинга виртуального DOM на реальный может быть произвольным. Стараются, конечно быть поближе к HTML, но священную корову из него не делают, не просто расширяя или урезая его, но и меняя синтаксис.

                        В общем и в целом, часть атрибутов передаётся as is, часть переформатируется в валидный код, часть урезается.
                  • –9
                    Кому надоел React, ставьте плюс. :)
                    Кому нравиться, пройдите мимо. :)
                    • –6
                      Просил же пройти мимо.
                      Нет же, еще и в карму насрало.
                      • +1
                        На хабре полно людей, никогда не проходящих мимо.

                        Особенно когда их об этом просишь. Специально не пройдут, ну и в карму специально не поленятся зайти.

                        Терпите. :-)
                      • –2
                        Астанавитесь. :)

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