Refs и DOM
Refs (далее просто «ссылки») предоставляет способ доступа к DOM-узлам или React-элементам, созданным в методе render()
.
В обычном потоке данных React свойства — единственный способ взаимодействия родительских компонентов со своими дочерними элементами. Чтобы изменить дочерний элемент, вы повторно отрисовываете его с помощью новых свойств. Тем не менее, есть несколько случаев, когда вам необходимо принудительно модифицировать дочерний элемент за пределами типичного потока данных. Изменяемый дочерний элемент может быть экземпляром компонента React или DOM-элементом. Для обоих этих случаев React предоставляет запасной вариант.
Когда использовать ссылки
Есть несколько хороших примеров использования ссылок:
- Управление фокусом, выделение текста или воспроизведение медиаресурсами.
- Выполнение анимаций в императивном подходе.
- Интеграция со сторонними библиотеками, взаимодействующие с DOM.
Избегайте использования ссылок для всего, что может быть сделано в декларативном стиле.
Например, вместо использования методов open()
и close()
в компоненте Dialog
, передайте ему свойство isOpen
.
Не злоупотребляйте использованием ссылок
Вашим первым побуждением для использования ссылок может быть в том, «чтобы они заработали» в вашем приложении. Если это так, остановитесь и подумайте о том, кому должно принадлежать состояние в иерархии компонентов. Часто становится ясно, что правильное место для «владения» этим состоянием находится на более высоком уровне в иерархии. Смотрите руководство по поднятию состояния для в качестве примера.
Примечание
Приведённые ниже примеры были обновлены, чтобы использовать API
React.createRef()
, введённый в React 16.3. Если вы используете более раннюю версию React, мы рекомендуем использовать ссылки-колбэки.
Создание ссылок
Ссылки создаются с использованием React.createRef()
и добавляются к React-элементам с помощью атрибута ref
. Ссылки обычно присваиваются свойству экземпляра, когда компонент создаётся таким образом, чтобы на них можно было ссылаться по всему компоненту.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
Доступ к ссылкам
Когда ссылка передаётся элементу в render()
, доступ к её узлу можно получить в атрибуте current
.
const node = this.myRef.current;
Значение ссылки отличается в зависимости от типа узла:
- Когда атрибут
ref
используется в HTML-элементе,ref
, созданный в конструкторе с помощьюReact.createRef()
, получает базовый элемент DOM в качестве своего свойстваcurrent
. - Когда атрибут
ref
используется на пользовательском классовом компоненте, объектref
получает примонтированный экземпляр компонента в качестве своего свойстваcurrent
. - Вы не можете использовать атрибут
ref
в функциональных компонентах, потому что у них не может быть экземпляров.
Приведённые ниже примеры демонстрируют данные различия.
Добавление ссылки на DOM-элемент
Этот код использует ref
для сохранения ссылки на узел DOM:
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// Создание ссылки для сохранения DOM-элемента textInput
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// Явная установка фокуса на поле ввода текста с использованием непосредственно API DOM
// Примечание: мы получаем доступ к "current" для получения DOM-узла
this.textInput.current.focus();
}
render() {
// Указать React, что мы хотим связать ссылку <input>
// со свойством `textInput`, созданного в конструкторе
return (
<div>
<input
type="text"
ref={this.textInput} />
<input
type="button"
value="Установить фокус на поле ввода текста"
onClick={this.focusTextInput}
/>
</div>
);
}
}
При монтировании компонента React присваивает свойству current
элемент DOM, и назначить ему обратно значение null
при размонтировании компонента. Обновления ref
происходят перед выполнением хук жизненного цикла componentDidMount
или componentDidUpdate
.
Добавление ссылки на классовый компонент
Если бы мы хотели обернуть показанный выше CustomTextInput
для имитации щелчка по нему сразу после монтирования, мы могли бы использовать атрибут ref
для доступа к пользовательскому полю ввода и вызвать вручную метод focusTextInput
:
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
componentDidMount() {
this.textInput.current.focusTextInput();
}
render() {
return (
<CustomTextInput ref={this.textInput} />
);
}
}
Обратите внимание, что это работает только в том случае, если CustomTextInput
объявлен как класс:
class CustomTextInput extends React.Component {
// ...
}
Ссылки и функциональные компоненты
Вы не можете использовать атрибут ref
на функциональных компонентах, потому что из них нельзя создать экземпляры:
function MyFunctionComponent() {
return <input />;
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
render() {
// Это *не* будет работать!
return (
<MyFunctionComponent ref={this.textInput} />
);
}
}
Вам нужно преобразовать функциональный компонент в классовый, если вам нужна ссылка на него, по аналогии с тем, когда вам нужны методы жизненного цикла или состояние.
Однако вы можете использовать атрибут ref
внутри функционального компонента, если вы ссылаетесь на DOM-элемент или классовый компонент:
function CustomTextInput(props) {
// textInput должен быть объявлен здесь, чтобы атрибут ref мог сослаться на него
let textInput = React.createRef();
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input
type="text"
ref={textInput} />
<input
type="button"
value="Установка фокуса на поле ввода текста"
onClick={handleClick}
/>
</div>
);
}
Предоставление DOM-ссылок на родительские компоненты
В редких случаях вам может потребоваться доступ к дочернему узлу DOM из родительского компонента. Обычно это не рекомендуется, так как это нарушает инкапсуляцию компонентов, но иногда может быть полезно для установки фокуса, определения размера элемента или положения дочернего DOM-узла.
Хотя вы можете добавить ссылку на дочерний компонент, это не идеальное решение, так как вы получите только экземпляр компонента, а не узел DOM. Кроме того, это не будет работать с функциональными компонентами.
Если вы используете React 16.3 или выше, мы рекомендуем использовать передачу ссылок для подобных случаев. Передача ссылок позволяет компонентам предоставлять ссылки любым дочерним компонентам. Вы можете найти подробный пример того, как передать дочерний DOM-узел родительскому компоненту в документации по передаче ссылок.
Если вы используете React 16.2 или ниже, или если вам требуется больше гибкости, чем предоставляется передачей ссылок, вы можете использовать такой альтернативный подход и явно передать ссылку в виде свойства с другим именем.
Когда это возможно, мы советуем не предоставлять доступ к узлам DOM, но это может быть полезным запасным вариантом. Обратите внимание, что для этого подхода вам необходимо добавить немного кода к дочернему компоненту. Если у вас нет абсолютно никакого контроля над реализацией дочернего компонента, последний вариант — использовать findDOMNode()
, но это не приветствуется и устарело в StrictMode
.
Ссылки-колбэки
React также поддерживает другой способ установки ссылок, называемый «ссылки-колбэки», который предоставляет более точное управление ссылками, когда они установлены и не установлены.
Вместо передачи атрибута ref
, созданногоcreateRef()
, вы передаёте функцию. Функция получает экземпляр компонента React или DOM-элемент HTML в качестве своего аргумента, который можно сохранить и получить в другом месте.
В приведённом ниже примере показано обычный паттерн: использование колбэка ref
для сохранения ссылки на узел DOM в свойстве экземпляра.
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = null;
this.setTextInputRef = element => {
this.textInput = element;
};
this.focusTextInput = () => {
// Установка фокуса на поле ввода текста, используя непосредственно API DOM
if (this.textInput) this.textInput.focus();
};
}
componentDidMount() {
// Автоматическая установка фокуса на поле ввода текста при монтировании
this.focusTextInput();
}
render() {
// Использование колбэка на `ref` для сохранения ссылки на DOM-элемента поля ввода текста
// в свойстве экземпляра (например, this.textInput).
return (
<div>
<input
type="text"
ref={this.setTextInputRef}
/>
<input
type="button"
value="Установка фокуса на поле ввода текста"
onClick={this.focusTextInput}
/>
</div>
);
}
}
React будет вызывать колбэк, переданный в атрибуте ref
с DOM-элементом при монтировании компонента и вызовет его со значением null
при размонтировании. Колбэки в атрибуте ref
вызывается перед хуками жизненного цикла componentDidMount
или componentDidUpdate
.
Вы можете передавать колбэки между компонентами, также как это возможно с объектом ссылок, создаваемых с помощью React.createRef()
.
function CustomTextInput(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}
class Parent extends React.Component {
render() {
return (
<CustomTextInput
inputRef={el => this.inputElement = el}
/>
);
}
}
В приведённом выше примере Parent
передаёт свой колбэк-ссылку в качестве свойства inputRef
в CustomTextInput
, а CustomTextInput
передаёт эту же функцию целевому атрибуту ref
элемента <input>
. В результате this.inputElement
в Parent
будет установлен на узел DOM, соответствующий элементу <input>
в CustomTextInput
.
Устаревшее API: ссылки в виде строк
Если вы раньше работали с React, вы можете ознакомиться с более старым API, где атрибут ref
— это строка, например "textInput"
, а к DOM-узлу доступ можно получить используя this.refs.textInput
. Мы не советуем использовать этот вариант, потому что у ссылок в виде строк есть ряд проблем, они считаются устаревшими и скорее всего будут удалены в одном из будущем релизе.
Примечание
Если вы используете
this.refs.textInput
для доступа к ссылками, мы рекомендуем использовать вместо этого ссылки-колбэки) или APIcreateRef
.
Предостережения по поводу ссылок-колбэков
Если колбэк ref
определён как встроенная функция, он будет вызываться дважды во время обновлений, первый раз со значением null
, а затем снова с DOM-элементом. Это связано с тем, что с каждой отрисовкой создаётся новый экземпляр функции, поэтому React необходимо очистить старую ссылку и настроить новую. Этого можно избежать, указав колбэк ref
как привязанный метод к классу, но обратите внимание, что в большинстве случаев это не имеет значения.