React 中 Refs 的基本概念

一句话回忆

Refs provide a way to access DOM nodes or React elements created in the render method.

When to Use Refs

There are a few good use cases for refs:

  • Managing focus, text selection, or media playback.
  • Triggering imperative animations.
  • Integrating with third-party DOM libraries.

Creating Refs

Refs 通过 React.createRef() 来创建,然后通过 ref 属性来附着在 React 元素上:

1
2
3
4
5
6
7
8
9
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}

Refs are commonly assigned to an instance property when a component is constructed so they can be referenced throughout the component.(在创建组件时,通常将 Refs 分配到一个实例的属性,这样就可以在整个组件的任何地方被访问到。)

个人理解:Refs 被创建后,我们要将 ref 属性绑定到组件中的某一个元素上,这样在整个组件的任何地方,都可以借助 Refs 来访问绑定的元素。可以类比于原生的 id 属性。

Accessing Refs

当 ref 被传递给一个渲染完成的元素时,我们就可以通过这个 ref 的 current 属性来引用对应的元素:

1
const node = this.myRef.current;

这里的 node 就是前面示例中的 div

不同类型的节点对应 ref 的值也不同:

  • When the ref attribute is used on an HTML element, the ref created in the constructor with React.createRef() receives the underlying(底层的) DOM element as its current property.
  • When the ref attribute is used on a custom class component, the ref object receives the mounted instance of the component as its current.
  • You may not use the ref attribute on function components because they don’t have instances.

Adding a Ref to a DOM Element

这段代码使用 ref 来保存对一个 DOM 节点的引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// create a ref to store the textInput DOM element
this.textInput = React.createRef();
}

focusTextInput = () => {
// Explicitly(显式地,直接地) focus the text input using the raw(原生的) DOM API
// Note: we're accessing "current" to get the DOM node
this.textInput.current.focus();
}

render() {
// tell React that we want to associate(结合) the <input> ref
// with the `textInput` that we created in the constructor
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 会在 componentDidMountcomponentDidUpdate 生命周期钩子触发前更新。

Adding a Ref to a Class Component

如果我们想包装上面的 CustomTextInput,来模拟它挂载之后立即被点击的操作,我们可以使用 ref 来获取这个自定义的 input 组件并手动调用它的 focusTextInput 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}

componentDidMount() {
this.textInput.current.focusTextInput();
}

render() {
return (
<CustomTextInput ref={this.textInput} />
);
}
}

Note that this only works if CustomTextInput is declared as a class:

1
2
3
class CustomTextInput extends React.Component {
// ...
}

Refs and Function Components

不能在函数组件上使用,但是可以在函数内部使用。

Callback Refs

Instead of passing a ref attribute created by createRef(), you pass a function. The function receives the React component instance or HTML DOM element as its argument, which can be stored and accessed elsewhere.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class CustomTextInput extends React.Component {
constructor(props) {
super(props);

this.textInput = null;

this.setTextInputRef = element => {
this.textInput = element;
};

this.focusTextInput = () => {
// Focus the text input using the raw DOM API
if (this.textInput) this.textInput.focus();
};
}

componentDidMount() {
// autofocus the input on mount
this.focusTextInput();
}

render() {
// Use the `ref` callback to store a reference to the text input DOM
// element in an instance field (for example, 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。在 componentDidMountcomponentDidUpdate 触发前,React 会保证 refs 一定是最新的。

You can pass callback refs between components like you can with object refs that were created with React.createRef().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function CustomTextInput(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}

class Parent extends React.Component {
render() {
return (
<CustomTextInput
inputRef={el => this.inputElement = el}
/>
);
}
}

In the example above, Parent passes its ref callback as an inputRef prop to the CustomTextInput, and the CustomTextInput passes the same function as a special ref attribute to the <input>. As a result, this.inputElement in Parent will be set to the DOM node corresponding to the <input> element in the CustomTextInput.

参考文档:

Refs and the DOM