3.3 Ссылки ref и DOM
Ссылки предоставляют способ доступа к узлам DOM или элементам React, созданным в методе отрисовки(render).
В типичном потоке данных React, свойства props
– это единственный способ
взаимодействия родительского компонента с его потомком. Чтобы модифицировать потомка,
вы перерисовываете его с новыми свойствами. Тем не менее, есть несколько случаев,
когда вам необходимо модифицировать потомок вне обычного потока данных. Потомок,
подлежащий модификации, может быть экземпляром React-компонента или являться DOM-элементом.
Для обоих этих случаев, React предоставляет «запасной выход».
Существует несколько оправданных случаев использования ссылок ref
:
- Управление фокусом, выделением текста или воспроизведением мультимедиа
- Переключение необходимой анимации
- Интеграция со сторонними DOM библиотеками
Избегайте использования ссылок для всего, что может быть реализовано декларативным путем!
К примеру, вместо публичных методов open()
и close()
на компоненте Dialog
,
передавайте в него свойство isOpen
.
По началу вы можете быть склонны использовать ссылки ref для того, чтобы «достигнуть результата» в вашем приложении. Если это так, то возьмите немного времени и подумайте более критично о том, кто должен владеть состоянием в иерархии компонентов. Часто, становится понятно, что правильное место, где должно находиться состояние, это более высокий уровень в иерархии. Смотрите главу «Передача состояния вверх по иерархии» в качестве примера.
Внимание!
Приведенные ниже примеры были обновлены, для возможности использования APIReact.createRef()
,
введенный в релизе 16.3. Если вы используете более раннюю версию React, мы
рекомендуем использовать API обратного вызова.
Ссылки создаются с использованием метода React.createRef()
и присоединяются к
элементам React с помощью атрибута ref
. Как правило, они назначаются свойствам экземпляра
компонента, в то время как компонент сконструирован таким образом, чтобы ссылки были
доступны из любого места этого компонента.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
Когда ref
передается элементу в методе render()
, ссылка на узел становится
доступной в атрибуте current
.
const node = this.myRef.current;
Значение ref
отличается в зависимости от типа узла:
-
Когда атрибут
ref
используется на HTML-элементе, объектref
, созданный в конструкторе с помощьюReact.createRef()
, в качестве значения своего свойстваcurrent
получает нативный DOM-элемент. -
Когда атрибут
ref
используется на пользовательском компоненте-классе, объектref
в качестве значения своего свойстваcurrent
получает монтированный экземпляр компонента. - Вы не можете использовать атрибут
ref
для компонентов-функций, так как они не имеют экземпляров.
Приведенные ниже примеры демонстрируют эти различия.
3.3.4.1 Добавление ссылки 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() {
// Явная фокусировка на текстовом поле, используя нативный DOM API
// Обратите внимание: мы осуществляем доступ к
// свойству current, чтобы получить DOM-узел
this.textInput.current.focus();
}
render() {
// Мы говорим React, что хотим ассоциировать атрибут ref элемента <input>
// с `textInput`, который мы создали в конструктооре
return (
<div>
<input
type="text"
ref={this.textInput} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
React присвоит свойству current элемент DOM, когда компонент будет монтирован,
и значение null
, когда компонент будет демонтирован. Обновления ref
происходят перед
срабатыванием методов ЖЦ componentDidMount
или componentDidUpdate
.
3.3.4.2 Добавление ссылки ref на компонент-класс
Если бы мы захотели обернуть компонент 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 {
// ...
}
3.3.4.3 Ссылки ref и функциональные компоненты
Нельзя использовать атрибут ref
на
компонентах-функциях, так как они не имеют экземпляров:
function MyFunctionalComponent() {
return <input />;
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
render() {
// This will *not* work!
return (
<MyFunctionalComponent 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="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
В редких случаях, вы можете захотеть получать доступ к DOM-элементам потомков из родительского компонента. Как правило, это не рекомендуется, так как разрушает инкапсуляцию компонента. Но изредка это может быть полезным для переключения фокуса, определения размера или позиции DOM-элемента потомка.
В то время как вы имеете возможность
добавлять ссылку на
компонент потомка,
это не
является идеальным решением, так как в коллбэке атрибута ref
вы можете получить только
экземпляр компонента, а не DOM-узел. Вдобавок, это не будет работать с функциональными компонентами.
Если вы используете React 16.3 и выше, для таких случаев мы рекомендуем использовать передачу ссылок. Передача ссылок дает компонентам свободу выбора, в предоставлении любых ссылок своих из потомков. Вы можете найти подробный пример того, как предоставить дочерний DOM-узел родительскому компоненту в документации по передаче ссылок.
Если вы используете React 16.2 и ниже, или если вам нужна большая гибкость, чем предоставленная передачей ссылок, вы можете использовать и явно передать ссылку как свойство с другим именем.
В целом, когда это возможно, рекомендуется не предоставлять доступ к узлам DOM,
но в некоторых ситуациях это может оказаться "безопасным выходом". Обратите внимание,
что для данного подхода вам необходимо добавить код к дочернему компоненту.
Если у вас нет абсолютно никакого контроля над реализацией дочернего компонента,
ваш последний вариант - использовать findDOMNode()
, но это не рекомендуется.
React также поддерживает и другой способ установки ссылок ref, называемый «callback refs» или «ref-коллбэки», который дает более гибкий контроль в моменты, когда ссылки установлены и не установлены.
Вместо передачи объекта ref
, созданного createRef()
, вы передаете функцию.
Функция получает экземпляр компонента React или HTML DOM-элемент в качестве своего аргумента.
Его можно сохранить и затем получить в любом другом месте компонента.
В приведенном ниже примере реализован общий паттерн: использование обратного
вызова в атрибуте ref
для сохранения ссылки на узел DOM в свойстве экземпляра.
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = null;
this.setTextInputRef = element => {
this.textInput = element;
};
this.focusTextInput = () => {
// Фокусировка на текстовом поле, используя нативный DOM API
if (this.textInput) this.textInput.focus();
};
}
componentDidMount() {
// автофокус на input при монтировании
this.focusTextInput();
}
render() {
// Используйте коллбэк для атрибута `ref`, чтобы сохранить
// ссылку на текстовый DOM-элемент input в свойстве экземпляра
// (например this.textInput)
return (
<div>
<input
type="text"
ref={this.setTextInputRef}
/>
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
Когда компонент будет монтирован, React вызовет коллбэк атрибута ref
,
передав ему в качестве аргумента DOM-элемент. При демонтировании коллбэк будет вызван с
аргументом равным null
. ref
-коллбэки вызываются перед методами жизненного
цикла componentDidMount
и componentDidUpdate
.
Вы можете передавать ref
-коллбэки между компонентами также, как и объекты,
созданные с помощью 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
передает свой ref
-коллбэк как
свойство inputRef
в CustomTextInput
, а CustomTextInput
передает эту же функцию как
специальный атрибут ref
в <input>
. В результате this.inputElement
в Parent
будет установлен на DOM-узел, соответствующий элементу <input>
в CustomTextInput
.
Если вы использовали React ранее, вы могли быть знакомы со старым API,
где ref
атрибут мог быть строкой, вроде "textInput
", и DOM-узел был доступен
как this.refs.textInput
. Мы рекомендуем избегать этого, потому что строковые
ссылки имеют некоторые проблемы, являются устаревшими и скорее всего будут
удалены в следующих релизах.
Внимание!
Если вы до сих пор используетеthis.refs.textInput
,
чтобы получать доступ к ссылкам, мы рекомендуем вместо этого использовать паттерн
callback
или
createRef API
.
Если коллбэк атрибута ref
определен как встроенная функция,
она будет вызываться дважды во время перерисовок: сперва с null
,
а затем снова с DOM-элементом. Это происходит потому, что во время каждой фазы
отрисовки создается новый экземпляр функции, поэтому React необходимо очистить
старый ref
и установить новый. Вы можете
этого избежать, определяя коллбэк как связанный метод в классе, но
обратите внимание, что в большинстве случаев это не имеет большого значения.