看到React 17中ref的用法发生了一些变化,记录一下。
这块内容在官网上是Ref转发和Refs & DOM的内容。
ref
ref这个东西有点反向引用的感觉。比较好的理解是ref={}这个写法,类似于把自定义的变量或者说标记打到组件或者DOM元素上去,这样标记的current属性就是被打标记的组件或者DOM元素。
要注意的是,ref和key一样,并不是普通的props属性,react对其进行了单独的处理。
ref提供了一种类似于反向引用的操作,有点别扭,也有固定的写法。官网推荐了一些适合ref的情景:
- 管理焦点,文本选择或媒体播放。
- 触发强制动画。
- 集成第三方 DOM 库。
在组件中操作render中的DOM元素
官方的例子举的是管理焦点,我来自己写个例子看看。
写一个input框和一个按钮,当按钮点击的时候,input框获得焦点,input框和按钮在同一个组件里。
class InputAndButton extends Component {
constructor(props, context) {
super(props, context);
this.inputRef = React.createRef();
}
inputFocus = ()=>{
this.inputRef.current.focus();
}
render() {
return (
<div>
<input type="text" ref={this.inputRef}/>
<button onClick={this.inputFocus}>聚焦</button>
</div>
);
}
}
export default InputAndButton;
这里渲染了两个元素,如果不使用ref,会发现button和input是同级的DOM元素,很难用button来控制input。
这里看代码的话,是先使用createRef
创建一个类的属性,然后在元素上使用特殊的ref={}来指定这个属性。看似是把inputRef赋给ref,实际上是把ref属性所在的元素,赋值给inputRef.current属性,在类中的其他地方,都可以使用inputRef.current来访问这个DOM。
这么写之后,按button,就可以控制input,这是比较方便的地方。
操作其他组件
其实可以把ref想象成一个标记,这个标记最后落到哪个组件或者DOM元素上,ref.current就指向那个东西。
看上一个例子,从类的角度来说,inputFocus是InputAndButton组件的一个方法,可以调用这个方法。
把ref标记在组件上的时候,ref.current就指向那个组件,由于组件是类,所以可以调用组件的方法。
这里我们另外再写一个组件,在其中把ref放到InputAndButton上去:
import React, {Component} from 'react';
import InputAndButton from "./InputAndButton";
class AComponent extends Component {
constructor(props, context) {
super(props, context);
this.sanerRef = React.createRef();
}
handleClick = ()=>{
this.sanerRef.current.inputFocus();
}
render() {
return (
<div>
<InputAndButton ref={this.sanerRef}/>
<button onClick={this.handleClick}>另外一个按钮也能获取焦点</button>
</div>
);
}
}
export default AComponent;
在我们的新组件中,有另外一个按钮,在这个组件中,又定义了一个sanerRef标记,然后把这个标记打到InputAndButton组件上,此时sanerRef.current就指向了InputAndButton组件,在handleClick中调用了InputAndButton类的方法inputFocus,结果就是input框获取了焦点。
注意此种方法,ref标记必须打在类组件上,不能是函数组件。
ref转发
这个意思就是说这个标记还可以被一层层的转发下去,从而让最上层的组件的标记,也能打到自己想要的深度,打到自己想要的元素或者组件上去。
ref转发需要使用函数式组件的写法,并且利用React库里一个特殊的方法,编写一个组件如下:
import React from 'react';
const MyInput = React.forwardRef(
((props, ref) => (<input type="text" ref={ref}/>))
);
export default MyInput;
这个组件的意思是,当外界有一个ref标记打到这个组件上的时候,这个组件会接力把标记打到input框上,所以最后那个标记实际上是打到了input框上。
把这个组件和其他组件结合起来使用:
import MyInput from "./MyInput";
class AComponent extends Component {
constructor(props, context) {
super(props, context);
this.sanerRef = React.createRef();
}
handleClick = ()=>{
console.log(this.sanerRef.current);
this.sanerRef.current.focus();
}
render() {
return (
<div>
<MyInput ref={this.sanerRef}/>
<button onClick={this.handleClick}>另外一个按钮也能获取焦点</button>
</div>
);
}
}
sanerRef的标记打在了MyInput组件上,此时sanerRef.current就是MyInput组件吗?不是的,这个标记被MyInput继续转发,最终打在了其中的input标签上,在控制台打印出的this.sanerRef.current是DOM元素,所以对其调用了原生的DOM API focus()来获取焦点。
好,对于ref,目前知道这么多基本足够了。