Продолжение серии переводов раздела "Продвинутые руководства" (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
Комментарии (24)
zShift
14.01.2017 12:45-3Опять невалидное. Почему бы им просто не использовать data-атрибуты. Каждый пытается свою спецификацию сделать…
faiwer
14.01.2017 14:24Дык, эти атрибуты же не добираются до DOM. Это внутренняя кухня.
zShift
14.01.2017 14:42-2Если честно просто не пользуюсь реактом(и ангуляром из-за несоблюдения стандартов), но в любом случае этих атрибутом нет в спецификации. Есть data-атрибуты. Вы хотите сказать, что в выхлопе на HTML они урезаются чтоли или переформируются в валидный код?
VolCh
14.01.2017 15:51+4В Реакте не используется HTML, JSX (JS+XML) не надмножество ни HTML, ни даже xHTML. Как говорится, все элементы и атрибуты XML вымышлены, все совпадения с HTML случайны. JSX-элементы «рендерятся» в вызовы JS-функций CreateElement, которые возвращают элементы виртуального DOM, которые потом «рендерятся» в реальный. HTML в процессе вообще не участвует (серверный рендеринг опустим для простоты), а алгоритм маппинга виртуального DOM на реальный может быть произвольным. Стараются, конечно быть поближе к HTML, но священную корову из него не делают, не просто расширяя или урезая его, но и меняя синтаксис.
В общем и в целом, часть атрибутов передаётся as is, часть переформатируется в валидный код, часть урезается.
http3
14.01.2017 21:06-9Кому надоел React, ставьте плюс. :)
Кому нравиться, пройдите мимо. :)http3
14.01.2017 22:17-6Просил же пройти мимо.
Нет же, еще и в карму насрало.taujavarob
16.01.2017 00:13+1На хабре полно людей, никогда не проходящих мимо.
Особенно когда их об этом просишь. Специально не пройдут, ну и в карму специально не поленятся зайти.
Терпите. :-)
TheShock
Как альтернатива — можно использовать строковое название и тогда не придется каждый вызов создавать анонимную функцию, не знаю, почему в статье нету этого примера.
edit: таки описано:
не хватает аргументов, почему рекомендуют.
vtikunov
В статье об этом написано:
vtikunov
Не надо давать уникальные имена универсальным шаблонным элементам, можно использовать универсальные функции. Представьте, что у вас 2 inputa и 2 кнопки. И там и там одинаковый функционал.
TheShock
Звучит как место для вывода кода в отдельный компонент. Но я не могу понять, что именно вы предлагаете.
VolCh
Как различать какие элементы демонтируются?
faiwer
У меня сложилось впечатление, что это для того, чтобы люди избегали использования ref как таковых, окромя тех ситуаций, когда иначе никак. Может быть с их точки зрения, люди злоупотребляют этой возможностью в ущерб react-way.
VolCh
Тогда надо сделать depricated, а потом убрать )
mester
Да, если добавить сюда ранее не документированный контекст, dangerouslySetInnerHTML={{ __html: «Hello» }}, вечные предупреждения при управлении div[contenteditable=«true»] — то получится сплошная политика запретов. VUE в этом плане куда свободнее
TheShock
И молодцы. Дали теперь возможность таким людям писать что-то вроде такого:
Архитекторы очень мудры
radist2s
Вопрос к эксперту: а разве такого рода даже анонимные функции не успешно ли оптимизируются движком и фактически функция заново не создается? Или все же она сразу же выбрасывается из памяти после выполнения
render
?TheShock
Если только с точки зрения теории, то стоит не забывать, что это не просто блок кода, а объект первого класса, он создается, к нему цепляется scope и prototype, его присваивают переменной. Чистые функции легче оптимизировать (там нет необходимости оставлять замыкание), эта функция грязная.
На практике даже самые современные браузеры не всегда очищают память, если на неё была замкнута функция даже если эта функция уже неактуальна (это что-то вроде перекресных ссылок). И тем больше вероятность чем сложнее приложение, а одна неудачная неочищенная ссылка тянет за собой целый ворох мусора. А код реакта как раз крайне нетривиален.
Более того, кроме чисто js-нюансов есть и важная особенность Реакта. Процитирую из документации:
То есть каждую перерисовку LoggingButton внутренний button будет получать НОВЫЙ экземпляр функции и потому Реакт будет считать, что его необходимо обновить, хотя можно было бы использовать закешированный (старый) вариант, если бы экземпляр функции с контекстом был создан и сохранен изначально.
пс. Есть очень хорошая статья, которая поможет понять, что происходит и какая работа выполняется при создании анонимной функции: http://dmitrysoshnikov.com/ecmascript/ru-chapter-6-closures/
SaitoNakamura
Следует понимать что это актуально только если реализовывается shouldComponentUpdate со сравнением функций переданных через props. Стандартная реализация shouldComponentUpdate просто возвращает true, поэтому компоненту глубоко плевать на то, новосозданная это функция или закешированная.
П.С. вот хорошая статья на тему производительности реакта http://blog.csssr.ru/2016/12/07/react-perfomance/
VolCh
Или если что-то типа Redux/MobX/… подставляет свою функцию, о чём можно даже не догадываться при поверхностном изучении. Второй вариант — несколько этих компонентов на странице, в такой форме будет создаваться новая функция для каждого элемента, а могла бы одна биндиться.
unel
Вот тут участники реакта немного проясняют свою позицию:
unel
Кстати, эту информацию уже добавили в официальную доку, неплохо бы наверное и перевод обновить ;-)
vtikunov
Спасибо. Обновил.