- Учим useRef на примерах — React Hooks
- useRef
- Как работать с useRef ?
- Заключение
- почему в этом примере не работает useRef?
- 2 ответа
- createRef, setRef, useRef и зачем нужен current в ref
- Вспоминаем setRef
- Вопросы к createRef
- Изучаем исходники createRef
- Изучаем исходники работы атрибута ref
- А что происходит в commitAttachRef при 2-ом рендере?
- А что если использовать createRef вместо useRef?
- Изучаем исходники useRef
- Сравниваем поведение при createRef и useRef
- Эксперимент 1. Сохраняем ту же ссылку на объект и обнуляем current
- Эксперимент 2. Изменяем ссылку на объект и сохраняем current
- Паттерн единого объекта с мутирующим свойством
- Мысли вслух
Учим useRef на примерах — React Hooks
Доброго времени суток, друзья. Продолжим тему React хуков и поговорим сегодня о useRef. Из названия самого хука уже может быть понятно, что он работает со ссылками. Основное его предназначение заключается в предоставлении доступа DOM для работы с дочерними элементами.
Помните, что само использование useRef не считается хорошим «тоном» и работа с DOM элементами напрямую в React должна происходить только тогда, кода других вариантов не осталось.
Видео на эту тему.
useRef
Для того, чтобы начать работать с ссылками в функциональных компонентах, React требуется инициализировать хук useRef, передав в него начальное значение.
После инициализации данный хук вернет ref-объект со свойством current.
Данная запись говорит нам, что переменная ref будет равна
Как работать с useRef ?
Разберем стандартный «кейс» использования useRef. Допустим у нас имеется форма с одним input и кнопкой по клику, на которую мы хотим устанавливать фокус на этот input.
Мы добавляем ref на элемент input, который будем контролировать. По нажатию на кнопку происходит вызов функции focus(), в которой мы обращаемся через свойство current к самому DOM элементу. Далее воспользуемся DOM API функцией focus() для вызова фокуса на элемент input.
В DOM API помимо работы с фокусом, можно еще добавлять новые элементы или удалять старые, а также работать с атрибутами элементов. Это прекрасная возможность изменения DOM в React, но эта же возможность полностью противоречит концепции работы виртуального DOM. Из-за таких изменений не происходит умного сравнения в виртуальном DOM, что часто приводит к ошибкам.
Заключение
Работа со ссылками в React с использованием useRef – очень удобный способ работы с DOM элементам. Хук useRef не приводит к повторному перерендериванию компонента даже когда происходит изменение данных в нем. C другой стороны злоупотребление работы со ссылками в React компонентах будет вести к нарушению работы в виртуальном DOM, о чем всегда необходимо помнить. Надеюсь, что данная статья была вам полезна. Учитесь, думайте, пишите код. Удачного кодинга, друзья!
Подписывайтесь на наш канал в Telegram и на YouTube для получения самой последней и актуальной информации.
Источник
почему в этом примере не работает useRef?
Вот мой код перехватчиков реакции:
Я подумал, что надо сделать дважды. в первый раз el.current должен быть нулевым, а второй раз — указывать на DOM-объект div. при запуске это был вывод
Да, рендеринг происходил дважды. однако второй ток рендеринга все еще равен нулю. Зачем?
Решение: как описано ниже Гиришем Кудипуди. Я добавил useEffect
2 ответа
Возможно, вы зациклились на количестве визуализаций , что не обязательно является лучшим мышлением React при работе с хуками. Мышление должно быть больше похоже на что изменилось в моем мире .
С этого момента попробуйте добавить useEffect и сообщите ему, что вам интересно узнать, когда ref изменится в моем мире. Попробуйте следующий пример и посмотрите сами.
React выполнит повторный рендеринг, когда ref будет инициализирован значением, но это не значит, что это произойдет при втором рендеринге.
Отвечая на ваш вопрос, вы не сказали, что делать при изменении ref .
ReactDOM.render (элемент, контейнер [, обратный вызов])
Визуализируйте элемент React в DOM в предоставленном контейнере и верните ссылку на компонент (или возвращает null для компонентов без состояния ).
Поскольку вы пытаетесь сослаться на функциональный компонент, это может быть причиной. Это интересный сценарий, с которым я столкнулся из-за вашего вопроса.
Также, если используется в качестве дочернего компонента, результат будет таким, как ожидалось
Источник
createRef, setRef, useRef и зачем нужен current в ref
В этой статье попробуем разобрать большинство непонятных базовых принципов при взаимодействии с ref . Например чем детально отличается createRef от useRef , зачем в этих объектах отдельное свойство current и многое другое. Одним словом попытаемся открыть много черных ящиков по работе с ref , из-за которых у вас возможно накопились вопросы. (Данная статья, является расшифровкой видео)
Вспоминаем setRef
Начнем с примера. Допустим у нас есть небольшой класс, где нам нужно работать с ref . В классовом компоненте, моим любимым способом работы с ref — является передача именно функции в атрибут ref :
Функция setRef первым параметром получит ноду этого элемента и у вас есть возможность сохранить ее в this . Я называю такой метод в своих проектах setRef , т.к. он визуально мне напоминает классический setter.
Почему такой подход мне нравится, в нем почти отсутствует магия. Вы контролируете почти весь код, разве что кроме момента передачи функции в атрибут ref и кто-то ее там вызывает, но и это выглядит вполне себе привычно, когда мы хотим получить значение после асинхронной операции с помощью callback.
Вопросы к createRef
А теперь, давайте сравним с альтернативным подходом. Будем использовать createRef для создания инстанса ref и хранить все там же в this .
Этот код, на мое мнение, немного сложнее предыдущего. Для начала, createRef() сохраняет что-то неизвестное в this.ref . После добавления console.log , мы видим там объект с одним свойством current равным null .
Далее мы этот объект < current: null >засовываем в атрибут ref и уже в componentDidMount имеем доступ к ноде.
Тут у меня возникает сразу несколько вопросов:
Зачем нам вообще свойство current ?
И если оно есть, почему тогда в ref мы не передаем this.ref.current ?
А вообще гарантировано ли существование свойства current ?
Да и вообще подход передачи объекта в ref , чтобы его там мутировали, не очень популярен в React, особенно если вы используете redux в своем проекте, где мутирование не приветствуется.
Изучаем исходники createRef
Чтобы во всем этом разобраться, я предлагаю изучить исходники реакта. Начнем с метода createRef() (ссылка).
Здесь мы видим, что создается объект с одним свойством current равным null , и он же возвращается. Крайне простой метод.
Таким образом, мы можем даже заменить метод createRef на просто создание объекта со свойством current . И это будет работать точно так же.
Более того для эксперимента я добавил в этот объект и дополнительное свойство count и оно не исчезло после прокидывания в ref .
Изучаем исходники работы атрибута ref
Чтобы разобраться что происходит внутри атрибута ref, мы опять обратимся к исходникам. Я какое-то время подебажил, чтобы найти место где происходит работа с присваиванием ref . И это место — функция commitAttachRef. Она находится в пакете react-reconciler в файле ReactFiberCommitWork.new.js.
Изучив этот код становится понятно, что полный сценарий работы с ref достаточно примитивный
Исходя из этих знаний, мы можем предположить, что скорей всего проверка на существование свойства current избыточна. Чтобы уже точно подтвердить это, я полез в типы:
И действительно поле current не имеет вопросительного знака, а это значит оно обязательное. Поэтому можете смело использовать поле current без дополнительных проверок.
А что происходит в commitAttachRef при 2-ом рендере?
Код который мы изучили, описывает только первый рендер, давайте разберемся что происходит с ref на второй и последующие рендеры.
Для этого было решено провести еще один эксперимент. В начале метода commitAttachRef я добавил console.log
А с другой стороны, доработал предыдущий пример с ref , а именно добавил счетчик внутри div-а . И описал классический метод incerement .
В браузере же мы увидим следующую картину:
При первом рендере вызывается console.log(‘commitAttachRef . ‘) . И дальше мы нажимаем несколько раз на кнопку увеличения счетчика, но метод commitAttachRef больше не вызывается.
Давайте еще доработаем пример и попробуем вставить значение счетчика в className этого div-а
И поведение в браузере не изменилось от такой доработки. commitAttachRef по прежнему вызывается лишь 1 раз.
Поэтому было решено еще доработать код, а именно положить кнопку и счетчик рядом с div-ом , который маунтим, только если число четное.
Перейдем теперь в браузер. И понажимаем снова на кнопку “плюс”. И в консоли видим, каждый раз когда число четное, перед did update вызывается “commitAttachRef . ” .
В принципе понять это не сложно. Пока мы меняем атрибуты, нода мутирует в виртуальном дереве и обновлять ref реакту смысла нет, т.к. ссылка в виртуальном дереве одна и та же, а когда по какой-либо причине происходит Mount / Unmount ноды, ссылка на ноду обновляется и соответственно нужно перезаписать ref . Таким образом, на первый взгляд метод commitAttachRef вызывается только если нода полностью меняется, но в действительности это не единственный случай. Рассмотрим для этого другие ситуации.
А что если использовать createRef вместо useRef?
В предыдущих экспериментах мы разбирали примеры использования createRef() на классах, а что будет если createRef() использовать в функциональных компонентах?
Поэтому я решил переписать предыдущий пример на хуках. Получилась следующая картина:
Вместо привычного useRef() мы подставим createRef() . И в useEffect на каждое изменения counter будем выводить значение counter и ref . Перейдем в браузер.
Мы видим, что абсолютно на каждый рендер вызывается метод commitAttachRef . Хотя ноду, как в предыдущих примерах, мы не меняли. При этом в useEffect нода вполне себе валидная, и указывает на правильный
Конечно же, если мы заменим createRef на useRef , тогда commitAttachRef будет вызываться только один раз. Чтобы понять почему это так работает нужно изучить исходники обоих методов и сравнить
Изучаем исходники useRef
Исходники createRef мы уже смотрели, давайте бегло изучим исходники useRef . Если вы читали мою предыдущую статью “Первое погружение в исходники хуков” вы знаете, что за одним хуком useRef кроется несколько методов, а именно mountRef , updateRef . Они достаточно примитивные.
В mountRef достается инстанс hook . И далее видим проверку на dev окружение. И когда мы проскролим весь этот большой блок для дев окружения, мы увидим, что для прод режима будут выполняться всего 3 строки: создать ref , сохранить его внутри хука и вернуть его.
Метод updateRef еще проще:
Нужно достать тот же хук из метода updateWorkInProgressHook() и вернуть сохраненный в нем ref .
Сравниваем поведение при createRef и useRef
Изучив хук useRef , мы можем наконец-то разобраться, почему useRef лучше подходит для функционального компонента, чем createRef . Перед тем как начнем сравнивать, я хочу ввести кое какие обозначения.
Для того чтобы показать, что создается именно новый объект, я решил ввести обозначение буквами. Если после какого-то действия буква возле объекта остаётся прежней, значит это та же ссылка на объект. Если же буква изменится, значит это полностью новый объект.
А теперь, построим временную сравнительную шкалу для сравнения useRef() и createRef()
Как мы видим, при первом рендере поведение у обоих методов абсолютно одинаковое, а вот второй рендер уже отличается. В случае useRef() в current мы имеем все ту же ноду указывающую на div , да и сам объект содержащий current все тот же «a». В случае createRef() создается как новый объект «c» (в первом рендере был «b»), так и свойство current снова равняется null . И как следствие вызывается commitAttachRef и на 2-ом рендере.
Возникает резонный вопрос. А что именно заставляет вызвать commitAttachRef ? Это то что ссылка на объект изменилась с «b» на «с» или это, потому что при втором рендере current снова стал null ?
Для разгадки этой тайны, проведем 2 мелких эксперимента:
Эксперимент 1. Сохраняем ту же ссылку на объект и обнуляем current
Рассмотрим следующий код:
Суть идеи в том, чтобы при 1-ом, 2-ом и последующих рендерах, в атрибут ref передавать current равный null . И посмотреть, будет ли вызываться commitAttachRef при каждом ренедере.
РЕЗУЛЬТАТ: commitAttachRef вызывается лишь 1 раз, при маунте компонент
Эксперимент 2. Изменяем ссылку на объект и сохраняем current
Рассмотрим следующий код:
Идея заключается в том, чтобы при каждом рендере менялась ссылка на объект передаваемый в атрибут ref , но при этом приходило валидное значение current .
РЕЗУЛЬТАТ: commitAttachRef вызывается на каждый рендер
Паттерн единого объекта с мутирующим свойством
Из этого можно сделать вывод, что на вызов commitAttachRef влияет именно ссылка на объект, который мы послали в атрибут ref . И сделано это не просто так. Более того можно проследить паттерн, который закладывали React Core разработчики.
useRef всегда создает объект лишь единожды, при первой инициализации и больше никогда не меняется, что помогает избежать дополнительных вызовов commitAttachRef . И эту ссылку можно использовать например в useEffect , и даже eslint, который заставляет нас дописывать в зависимости, все что мы используем внутри, абсолютно не требует дописывать ref в зависимости, т.к. ссылка всегда одинаковая, хоть current может и меняться. А это значит, мы не получим дополнительных вызовов useEffect , если current изменится, но при желании можем в зависимости и добавить ref.current и получим дополнительные вызовы useEffect (но eslint на такое использование ref.current ругается, т.к. в большинстве случаев, это приведет скорее к багам, чем к осознанной пользе). Получается данный патерн дает нам определенную дополнительную гибкость.
Так же ref удобно использовать и как props . И при изменении current , ваш компонент не будет перерисовываться, если вы этого не хотите. Поэтому конструкция объекта с дополнительным свойством current , это не просто так исторически сложилось, а осознанный паттерн, которым предлагают пользоваться нам React Core разработчики.
Мысли вслух
Идея данной статьи — это чуть лучше разобраться в том, как работает инструмент, которым мы вкалачиваем гвозди каждый в свой проект. Да, после этой статьи маловероятно, что вы начнете писать свой проект как то иначе, но однозначно станете более уверенно писать привычные вам конструкции, т.к. будете осознавать, что именно происходит под капотом. И конечно, я надеюсь вас немного меньше станет раздражать наличие current в ref , т.к. это жертва во имя гибкости.
Источник