浅谈 React Refs
本文作者:IMWeb eden 原文出处:IMWeb社区 未经同意,禁止转载
React Refs
在React组件中,props是父组件与子组件的唯一通信方式,但是在某些情况下我们需要在props之外强制修改子组件或DOM元素,这种情况下React提供了Refs解决
哪些场景会用到refs
下面列举几个场景:
- 对input/video/audio需要控制时,例如输入框焦点、媒体播放状态
- 直接动画控制
- 集成第三方库
注意:如果能使用props实现,应该尽量避免使用refs实现
Refs三种方式
- 字符串模式 :废弃不建议使用
- 回调函数
- React.createRef() :React16.3提供
下面分别介绍每一种方式
字符串模式
class List extends React.Component {
constructor(props, context) {
super(props, context);
}
componentDidMount(){
this.refs.inputEl.focus();
}
render() {
const { list } = this.props;
return (
<input ref="inputEl"/>
);
}
}
存在的问题
- 针对静态类型检测不支持
- 对复杂用例难以实现:需要向父组件暴露dom;单个实例绑定多个dom
- 绑定到的实例,是执行render方法的实例,结果会让人很意外,例如:
class Child extends React.Component {
render() {
const { renderer } = this.props;
return <div>{renderer(1)}</div>;
}
}
class App extends React.Component {
render() {
return (
<div className="App">
<Child renderer={index => <div ref="test">{index}</div>} />
</div>
);
}
}
上面这种情况,会导致test绑定的实例时Child上面,并不是App上
回调函数模式
相比 字符串模式 更加灵活,也避免了诸多问题
- 可以优雅在组件销毁时回收变量, ref中的回调函数会在对应的普通组件componentDidMount,ComponentDidUpdate之前; 或者componentWillUnmount之后执行,componentWillUnmount之后执行时,callback接收到的参数是null
- 很好的支持静态类型检测
- 针对数组遍历时可以直接转换为对应的数组,看下面的例子
class List extends React.Component {
constructor(props, context) {
super(props, context);
}
_ref = el => {
if (el) {
if (!this.els) {
this.els = [];
}
this.els.push(el);
} else {
this.els = [];
}
};
render() {
const { list } = this.props;
return (
<ul>
{list.map((item, index) => {
return (
<li ref={this._ref} key={index}>
{item}
</li>
);
})}
</ul>
);
}
}
class App extends React.Component {
state = {
value: "",
list: []
};
onChange = ({ target: { value } }) => {
this.setState({ value });
};
add = () => {
const { list, value } = this.state;
list.push(value);
this.setState({
value: "",
list
});
};
render() {
const { value, list } = this.state;
return (
<div className="App">
<input value={value} onChange={this.onChange} />
<button onClick={this.add}>add</button>
<List list={list} />
</div>
);
}
}
- 可以将父组件的ref 传入 孙组件,虽然不建议这么使用(破坏组件封装)
function Input(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}
class InputBox extends React.Component {
_ref = (el)=>{
this.inputElement = el
}
render() {
return (
<Input
inputRef={this._ref}
/>
);
}
}
同样存在弊端
通常为了绑定一个组件(元素)实例到当前实例上需要写一个函数,代码结构上看起来很冗余,为了一个变量,使用一个函数去绑定,每一个绑定组件(元素)都需要一个方法处理,大材小用
因此React 提供了更简单有效的解决方案React.createRef()
React.createRef()
使用 React.createRef() 创建 refs,通过 ref 属性来获得 React 元素。当构造组件时,refs 通常被赋值给实例的一个属性,这样你可以在组件中任意一处使用它们.
class Test extends React.Component{
myRef = React.createRef();
componentDidMount(){
// 访问ref
const dom = this.myRef.current
}
render(){
return (
<div ref={this.myRef}/>
)
}
}
试试不是比创建一个回调函数更加简洁了?
ref的值取决于节点的类型:
- 当 ref 属性被用于一个普通的 HTML 元素时,React.createRef() 将接收底层 DOM 元素作为它的 current 属性以创建 ref 。
- 当 ref 属性被用于一个自定义类组件时,ref 对象将接收该组件已挂载的实例作为它的 current 。
- 你不能在函数式组件上使用 ref 属性,因为它们没有实例。
Refs传递
- 额外参数传递
class Sub extends Component{
render(){
const {forwardRef} = this.props;
return <div ref={forwardRef}/>
}
}
class Sup extends Component{
subRef = React.createRef();
render(){
return <Sub forwardRef={this.subRef}/>
}
}
将父组件ref作为一个props传入,在子组件显示调用
- React.forwardRef
class Sub extends Component{
render(){
const {forwardRef} = this.props;
return <div ref={forwardRef}/>
}
}
function forwardRef(props, ref){
return <Sup {...props} forwardRef={ref}/>
}
// 为了devtool中展示有意义的组件名称
forwardRef.displayName=`forwardRef-${Component.displayName||Component.name}`
const XSub = React.forwardRef(forwardRef);
class Sup extends Component{
_ref=(el)=>{this.subEl =el};
render(){
return <XSub ref={this._ref}/>
}
}
React.forwardRef方式,对于使用组件者来说,ref是透明的,不需要额外定一个props传入,直接传递到了下级组件,作为高阶组件封装时,这样做更加友好.
实现原理
首先我们看看String Refs的实现
String refs 在组件挂载、更新之前会被替换为一个函数如下图,因此实际上,String refs只是 函数是Refs的一种特殊场景
Ref在何时被赋值?
在源码中有两个方法commitAttachRef
挂载实例,commitDetachRef
卸载实例。commitAttachRef
当组件渲染完毕(componentDidMount/comonentDidUpdate)后被执行; commitDetachRef
则在组件或元素被销毁前执行(componentWillUnmount之前),清理引用
挂载: 如果是方法直接执行并传入实例,否则就是采用createRef创建的对象,作为挂在点
卸载:方法被传入
null
值,createRef方式就将current赋值null
,因此我们在使用函数模式时要注意传入null时需要清理引用,有的场景我们会将多个实例绑定到一个同一个对象或数组上。
总结
Refs 字符串模式已经废弃,React 不建议使用并且会提示警告,开发中推荐使用React.forwardRef方式,简单优雅,回调函数模式应用在复杂场景中。