- Анатомия React. Урок 12. Что такое ref’ы и как ими пользоваться?
- Ref как строка
- Ref как callback
- Ref как вызов createRef
- Персональные уроки по скайпу от MakeWeb
- Avocode — замена Photoshop для верстальщика
- Компонент React не работает
- 3.3 Ссылки ref и DOM
- 3.3.1 Когда использовать ссылки ref
- 3.3.2 Не злоупотребляйте ссылками ref
- 3.3.3 Создание ссылок
- 3.3.4 Доступ к ссылкам
- 3.3.4.1 Добавление ссылки ref на DOM-элемент
- 3.3.4.2 Добавление ссылки ref на компонент-класс
- 3.3.4.3 Ссылки ref и функциональные компоненты
- 3.3.5 Предоставление ссылок на DOM-узлы родительским компонентам
- 3.3.6 Ref-коллбэки
- 3.3.7 Старый API: строковые ссылки ref
- 3.3.8 Предостережения
Анатомия React. Урок 12. Что такое ref’ы и как ими пользоваться?
Разбираемся с ref’ами, которые позволяют добираться до конкретных DOM-нод и производить с ними различные манипуляции.
Давайте рассмотрим так называемые ref’ы. На самом деле это просто сокращение от английского reference — ссылка.В сущности они просто позволяют получить доступ к тем нодам внутри компонента, которые были созданы в результате выполнения метода render().
Это бывает нужно по разным причинам:- при работе с API для выделения текста или с фокусом текстовых полей,- произведения каких-то анимаций, перемещений и изменений внешнего вида путем прямого взаимодействии с нодой (хотя рекомендуется маскимально этого не делать), замеров размеров DOM-нод,- при работе с внешними библиотеками — скажем с jQuery (хотя уж его точно не стоит использовать в современных React-приложениях).
Поначалу новички, особенно те, кто перешел с jQuery на React могут злоупотреблять использованием рефов, однако со временем все становится на свои места.
Итак, исторически ref’ы в React прошли эволюционный путь, состоящий из трех этапов.
Ref как строка
Сначала ref’ы задавались строкой. Сразу замечу, что этот синтаксис устаревший, так что никогда его не используйте в современных приложениях. А выглядело это так:
Здесь у целевой ноды, к которой мы хотим обратиться, стоит специальный атрибут 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 и установить новый. Вы можете этого избежать, определяя коллбэк как связанный метод в классе, но обратите внимание, что в большинстве случаев это не имеет большого значения.
Источник