React не работает ref

Анатомия React. Урок 12. Что такое ref’ы и как ими пользоваться?

Разбираемся с ref’ами, которые позволяют добираться до конкретных DOM-нод и производить с ними различные манипуляции.

Давайте рассмотрим так называемые ref’ы. На самом деле это просто сокращение от английского reference — ссылка.В сущности они просто позволяют получить доступ к тем нодам внутри компонента, которые были созданы в результате выполнения метода render().

Это бывает нужно по разным причинам:- при работе с API для выделения текста или с фокусом текстовых полей,- произведения каких-то анимаций, перемещений и изменений внешнего вида путем прямого взаимодействии с нодой (хотя рекомендуется маскимально этого не делать), замеров размеров DOM-нод,- при работе с внешними библиотеками — скажем с jQuery (хотя уж его точно не стоит использовать в современных React-приложениях).

Поначалу новички, особенно те, кто перешел с jQuery на React могут злоупотреблять использованием рефов, однако со временем все становится на свои места.

Итак, исторически ref’ы в React прошли эволюционный путь, состоящий из трех этапов.

Ref как строка

Сначала ref’ы задавались строкой. Сразу замечу, что этот синтаксис устаревший, так что никогда его не используйте в современных приложениях. А выглядело это так:

Читайте также:  Отремонтировать пульт от телевизора samsung smart tv

Здесь у целевой ноды, к которой мы хотим обратиться, стоит специальный атрибут ref и его значение — строка. А эта строка фигурирует и вот здесь — в методе handleSubmit, срабатывающем когда мы отправляем форму. То есть this.refs это специальное поле в нашем инстансе компонента, которое хранит все подобные рефы, которые определены внутри этого компонента. Оттуда к ним доступ и осуществляется.

Ref как callback

Следующим этапом стала передача функции в атрибут ref.

Здесь в ref передается уже не строка, а колбэк, который после своего выполнения (после первого рендера), в this.textInput сохраняет ссылку на нативную браузерную DOM-ноду, к которой в componentDidMount мы и обращаемся вызывая метод focus. Это, после того как компонент замаунтился, заставляет его поймать фокус.

Вы можете быть уверены, что ДО выполнения методов жизненного цикла componentDidMount и componentDidUpdate, ref’ы уже будут установлены.

Ref как вызов createRef

Ну и наконец самым современным и рекомендуемым способом является вызов в конструкторе метода React.createRef():

Тут вообще все просто. Вызов обозначенной выше функции создает в this.inputRef структуру данных, которая пердназначена для хранения ref’а. А в render в ref передается ссылка на эту структуру в классе компонента. И вот, ref уже готов к использованию. Пишем this.inputRef.current и получаем ссылку на DOM-ноду.

Видно, что в консоли после выполнения componentDidMount появилось содержимое инпута.

Еще момент — мы можем в дочерний компонент передать ref как один из пропсов, который будет называться иначе чем ref, и затем уже в дочернем установить его на конкретный элемент, чтобы получить из родителя доступ к ноде дочернего элемента.

Данный код демонстрирует, как колбэк, устанавливающий в Parent ссылку на DOM-ноду мы можем отправить в дочерний компонент. Это происходит вот здесь, в пропсе setTextInputRef. Этот колбэк мы отправляем в пропс ref компонента CustomTextInput и так как этот колбэк объявлен в родительском компоненте, то и ссылка на DOM-ноду установится там же. Соответственно, после componentDidMount инпут нормально зафокусится. Как и в примере выше.

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

А вот еще более интересный пример:

Тут мы пропс ref пишем прямо у компонента, а не у известного браузеру HTML-элемента. И это работает иначе.

Компонент Parent уже рассмотренным ранее способом готовит в конструкторе поле для ref’а, затем в рендере ссылка создается. И если посмотреть, то она будет указывать на React-компонент, а не на DOM-ноду как в предыдущих случаях. Вот это и есть преимущество, так как получив такую ссылку, мы можем у CustomTextInput вызывать его методы. По нажатию на кнопку фокуса такой метод focusInput у CustomTextInput как раз и вызывается из Parent. А CustomTextInput уже внутри себя имеет ref на конкретный HTML-элемент инпут и может через this.inputRef.current вызвать нативный браузерный метод focus. И вот, наша задача достигнута.

Замечу, что такое не пройдет с компонентами, объявленными как функции, в отличие от тех, что объявлены в качестве класса. Такое просто-напросто не будет работать. Ведь нет инстанса и не у кого вызывать метод.

Но если же компонент у вас все же объявлен как функция, в него можно пробросить другую функцию, устанавливающую ref в классе родителя и работать с этим так же. Это было уже рассмотрено ранее.

Вообще же, в компоненте, объявленном как функция, можно использовать ref’ы:

Вместо использования конструктора мы просто создаем переменные — с инициализацией структуры под ref и с обработчиком клика по кнопке.

Ну и давайте еще вспомним для полноты картины про метод ReactDOM.findDOMNode. Он получает в качестве аргумента React-компонент и позволяет покопаться в DOM-содержимом его инстанса (ну или экземпляра — напоминание для тех кто еще не запомнил, что это одно и то же).

Я модифицировал предыдущий пример и добавил в обработчик клика кнопки Focus вывод в консоль результата работы ReactDOM.findDOMNode, которой я передал ссылку на наш компонент CustomTextInput, а точнее на его инстанс — не забываем про current в конце — и это дает нам DOM-ноду, с которой можно делать все, что позволено в браузере делать с DOM-нодой.

Например, давайте поменяем её текст. Вот так — взяли её инстанс, поискали по классу с помощью querySelector и поменяли innerText. Результат вы видите на экране.

Но правда лучше использовать эту штуку в самом крайнем случае, когда ну ничего уже не помогает. В специальном ограниченном режиме Strict Mode реакта, это работать перестанет. Про Strict мы еще поговорим.

Теперь про так называемый Ref Forwarding. О чем идет речь? Рассмотрим такую кнопку:

Рисуется обычный button и то, что внутри него, соответственно также отрисуется благодаря props.children — нам уже это известно. По-хорошему, один компонент не должен вмешиваться в разметку другого и менять стили или тем более структуру его дерева элементов. Но это больше справедливо для высокоуровневых компонентов вроде комментариев или календаря какого-нибудь. Для простых же компонентов вроде стилизованной кнопки или инпута, это удобно, чтобы изменить фокус, допустим. Или как-то анимировать. Или выделить текст.

И вот тут-то Forwarding Ref приходятся как нельзя кстати. Да, по-русски это можно перевести как «пробрасываемые рефы».

Данный пример наглядно демонстрирует особенности пробрасываемых ref’ов. С помощью метода React.forwardRef мы создаем компонент нашей кнопки и в него пробрасываются не только пропсы, а еще и ref, который для нас эта обертка и создала. Далее этот реф пробрасывается в button.

Затем, когда мы используем в другом компоненте нашу кастомную кнопку, то пробрасываем ей ref, который создаем уже привычным способом. При маунтинге компонента App, наша кнопка прекрасно получает фокус. Все работает.

Эта техника хорошо применима в компонентах высшего порядка (higher-order components) — я их называю просто и удобно — ХОК — от соответствующего акронима.

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

index.js:

App.js:

LogProps.js (неработающий вариант):

LogProps.js (работающий вариант):

CustomBtn.js:

В index.js просто рисуется компонент App.

App рисует CustomBtn с надписью SENDING внутри, благодаря props.children. Тут же, создается ref и отправляется в пропс ref компонента CustomBtn. Как только App маунтится, он через этот реф у CustomBtn вызывает метод changeSendingStatus.

Дальше самое интересное. HOC-компонент LogProps. Его назначение, выводить пропсы любого компонента, который им обернут. В нашем случае CustomBtn как раз имеет обертку в виде logProps. И мы для начала посмотрим на неработающий вариант и поймем почему он не работает.

Тут у нас объяалена функция logProps с маленькой l. В нее пробрасывается тот, компонент, который нужно обернуть. Внутри объявляется класс LogProps с большой L. Он рендерит WrappedComponent, а это в нашем случае будет CustomBtn, и пробрасывает в него все пропсы, которые были проброшены этажом выше. Как только данный компонент-обертка маунтится, он все пропсы выводит — как предыдущие, так и текущие. И компонент LogProps возвращается из функции. Почему — сейчас поймем.

Оборачиваемый компонент CustomBtn. Вот тут, в экспорте, мы нашу кнопку оборачиваем заимпорченным logProps.

Смотрим еще раз на цепочку. При экспорта выполняется функция logProps, получая аргументом наш CustomBtn. Тут он уже называется WrappedComponent и возвращается из компонента LogProps, а уже компонент LogProps, завязанный на WrappedComponent, возвращается из функции logProps. И попадает в экспорт из CustomBtn. Вот так и получается что отрисованный в App.js CustomBtn уже обернут в logProps.

Но скользкий момент в том, что несмотря на то, что мы передали в CustomBtn ref, он передается в компонент обертку и не пробрасывается дальше, в сам CustomBtn, так как расспредивание this.props не затрагивает специальный реактовский пропс ref и он в этот список просто не включается. При попытке запустить код в этом случае, мы увидим, что ref в App.js будет указывать на LogProps. В этом-то и загвоздка.

И здесь на помощь приходит как раз ref forwarding. Вот измененный код logProps. Вот тут, вместо того, чтобы просто возвратить компонент LogProps, мы возвращаем результат выполения React-метода forwardRef, который получает аргументом колбэк. Туда пробрасываются пропсы и ref, тот самый, который ранее нам был недоступен. Пропсы деструктурируются отдельно, а ref отправляется в отдельный пропс forwardedRef. И тогда внутри LogProps в методе render, мы можем из его уже пропсов извлечь отдельно forwardedRef и остальные пропсы. forwardedRef попадает в ref, а остальные пропсы деструктурируются отдельно.

И вот теперь явно видно, что в App.js ref указывает на CustomBtn. Внутри него создается свой ref, который указывает на button и метод changeSendingStatus, где меняется текст кнопки на SENT. Кстати, плохой пример, потому что по идее текст внутри зависит от this.props.children, а тут прямое вмешательство с перезаписью этого текста. В общем, вы поняли как не надо делать =)

Кстати, смотрите как выглядит React-дерево для этого примера. Вся наша структура дополнительно обернулась еще и в ForwardRef. Мы можем вместо стрелочной безымянной функции пробросить сюда функцию с именем:

Тогда в скобочках у ForwardRef это имя появится. Удобно для отладки.

А можно сделать еще круче:

Выносим данную функцию отдельно, назвав, например, forwardRef. Затем формируем имя оборачиваемого компонента в переменной name. Для этого берем у него значение displayName или name, если первого нет. дальше приписываем logProps и в скобках интерполируем name. И наконец, вызываем React.forwardRef с «пропатченной» функцией forwardRef.

Что ж.. Надеюсь материал данного урока не поплавил ваш мозг. Если так, то пересмотрите это видео несколько раз с промежутком в несколько дней, попутно запуская самостоятельно, а лучше набирая ручками, все примеры из урока. Всем удачи и встретимся на следующем занятии по React’у. Будет еще интереснее.

Персональные уроки по скайпу от MakeWeb

Avocode — замена Photoshop для верстальщика

При регистрации используй инвайт «nikita» и получи скидку 10% на первую покупку!

Источник

Компонент React не работает

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

Когда я утешаю журнал, он показывает, что e.target.offsetLeft и e.target.offsetTop равны 0, но я дал ему преимущество с обеих сторон.

Но на самом деле, когда я помещаю этот код, который должен размещать всплывающую подсказку, вся всплывающая подсказка не отображается:

И это весь мой код:

И в этот момент я не могу даже консоль регистрировать объект опций:/

Это не является исправлением ошибки в коде, но я излагаю некоторые принципы и функции React, которые помогут вам решить ваши проблемы только с помощью React (вместо того, чтобы смешивать собственные API DOM и React API).

Не рекомендуется напрямую обращаться к элементам DOM, используя собственные API DOM, когда вы используете React. Обработка DOM — это работа React. Для этого и нужен Реакт. Поэтому, если вы изменяете/удаляете/вставляете элементы из/в элементы, созданные с помощью React, вы теряете все преимущества этой мощной библиотеки; минимальное изменение DOM.

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

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

Пример использования ref :

В приведенном выше примере, если вы хотите, чтобы offsetWidth , offsetHeight или любые другие свойства DOM элемента , вы можете получить к нему доступ this.textInput.offsetWidth , this.textInput.offsetHeight и т.д. Но относитесь к ним как к чтению.

Если вы хотите изменить стили: добавьте атрибут style к элементу вашего JSX и измените встроенные стили, используя методы React State и Lifecycle.

Я также видел в вашем коде, что вы используете .removeChild и .appendChild , чтобы скрыть/показать подсказку. Вместо этого используйте React Conditional Rendering.

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

Источник

3.3 Ссылки ref и DOM

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3.3.6 Ref-коллбэки

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

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

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

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

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

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

3.3.7 Старый API: строковые ссылки ref

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

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

3.3.8 Предостережения

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

Источник

Оцените статью