什么是ref

ref 就是 reference(引用),当我们希望直接使用 DOM 元素中的某个方法(例如许多 H5 的 API)或者希望直接使用自定义组件中的某个方法。

先来看看 ref 大致怎么使用,可以很明了的看出 ref 就是组件的一个属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class App extends React.Component {

componentDidMount() {
console.log(this);
}

render() {
return (
<div>
<h1 ref="text">hello world</h1>
</div>
)
}
}

ref 的使用组件要求:

  1. ref 作用于内置的 html 组件,得到的将是真实的 DOM 对象

  2. ref 作用于类组件,得到的将是类的实例

  3. ref 不能作用于函数组件

ref 的用法

就像上面的例子,可以直接给一个 字符串,则 ref 则会成为类组件的 refs 属性的属性,传入的字符串则为 key,但是不推荐这种用法,即将被官方淘汰。而且,函数组件无法拥有这种类型的 ref

对象

通过 React.createRef 函数创建,该函数返回一个对象,然后将该对象传给给组件的 ref 属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const ref = React.createRef();

function App() {

setTimeout(() => {
console.log(ref);
}, 0);

return (
<div>
<h1 ref={ref}>hello world</h1>
</div>
)
}

其实 React.createRef 函数做的事很简单,就是创建了一个 { current: null } 样子的对象,最后 current 属性值会变成我们需要的值。

其实我们不使用该函数,直接给 ref 变量赋值为一个这样的字面量也可以。

函数

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
class CmpA extends React.Component {
render() {
return (
<div>
CmpA
</div>
)
}
}

function App() {

let refA = null;

setTimeout(() => {
console.log(refA);
}, 0);

return (
<div>
<CmpA ref={ el => refA = el}/>
</div>
)
}

函数调用的时间

在类组件的生命周期中调用时间

  1. componentDidMount 的之前会调用该函数

    componentDidMount 事件中可以使用 ref

  2. 如果 ref 的值发生了变动(旧的函数被新的函数替代),分别调用旧的函数以及新的函数,时间点出现在componentDidUpdate之前

    1. 旧的函数被调用时,传递 null

    2. 新的函数被调用时,传递对象

  3. 如果 ref 所在的组件被卸载,会调用函数

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
class App extends React.Component {

state = {
show: true
}

componentDidMount() {
console.log('componentDidMount', this.text);
}

render() {
const { show } = this.state;
const showContent = show ? <h1 ref={el => {
console.log('ref', el);
this.text = el;
}}>hello world</h1> : null;
return (
<div>
{showContent}
<button onClick={() => this.setState({})}>改变函数</button>
<button onClick={() => this.setState({
show: !show
})}>显示/隐藏</button>
</div>
)
}
}

ref 转发

有些时候我们并不需要直接使用 ref,而是想传递给子组件,特别是使用了 HOC 的情况下,HOC 不能污染子组件,这个时候直接传递 ref,就不能达到我们的要求,就需要 ref 转发(ref forward)

使用 ref 转发需要使用 React.forwardRef 函数,该函数接收一个函数组件,函数组件有两个参数(props,ref),在函数组件中我们可以控制 ref 转发给是谁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const Cmp = React.forwardRef((props, ref) => (
<h1 ref={ref}>hello world</h1>
));

class App extends Component {

ref = React.createRef();

componentDidMount() {
console.log(this.ref);
}

render() {
return (
<div>
<Cmp ref={this.ref} />
</div>
)
}
}

在 HOC 中使用的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function withLog(Comp) {
class LogWrapper extends React.Component {
componentDidMount() {
console.log(`日志:组件${Comp.name}被创建了!${Date.now()}`);
}
componentWillUnmount() {
console.log(`日志:组件${Comp.name}被销毁了!${Date.now()}`);
}
render() {
const { forwardRef, ...rest } = this.props;
return (
<>
<Comp ref={forwardRef} {...rest} />
</>
)
}
}

return React.forwardRef((props, ref) => {
return <LogWrapper {...props} forwardRef={ref} />
})
}