React先进的开发思想一直为社区所称道,基于数据流的设计极大地简化了前端开发成本。但如同官方文档所述,web开发中有很多场景需求是脱离数据流的,典型的如处理文本输入框聚焦(focus)。为此React提供了refs供开发者使用。
笔者是在2015年下半年开始学习React,那时候官方文档关于refs的介绍和使用与现在完全不同。最新的React版本(v15.5.4)已经对这个API进行了修改并更新,不过我们仍可以在文档中找到老版本API的蛛丝马迹:
我们来比较下新老refs有哪些异同:
在旧版本中,如上图所述,refs的使用非常简单,因为每个组件实例都有一个this.refs
属性,会自动引用所有包含ref属性组件的DOM,所以我们只需要在目标组件上添加一个自定义的ref
,然后进行使用即可:
class Button extends Component {
constructor(props){
super(props);
}
componentDidMount = () => {
let btn = this.refs.btn;
let link = this.refs.link;
}
render(){
return (
)
}
}
复制代码
文档加粗部分提醒到,将会在未来的某个版本把这种用法完全移除掉,建议开发者升级版本后使用新的ref。那么新的ref如何使用呢?
第一个重点是将ref改为回调函数的方式去使用。
直接上代码:
class Input extends Component {
constructor(props){
super(props);
}
focus = () => {
this.textInput.focus();
}
render(){
return (
{ this.textInput = input }} />
)
}
}
复制代码
这里我们可能就有第一个疑问了,input
参数是哪来的?文档中这样解释:
这就说明,当我们在DOM Element中使用ref
时,回调函数将接收当前的DOM元素作为参数,然后存储一个指向这个DOM元素的引用。那么在示例代码中,我们已经把input
元素存储在了this.textInput
中,在focus
函数中直接使用原生DOM API实现focus聚焦。
那么第二个疑问出现了,回调函数什么时候被调用?
答案是当组件挂载后和卸载后,以及ref属性本身发生变化时,回调函数就会被调用。
第二个重点是,可以在组件实例中使用`ref`。
前面的示例代码是在DOM添加ref
属性,那么我们来看看如何在组件实例中使用。再上代码:
//来源于上面的示例代码?
class AutoFocusTextInput extends Component {
componentDidMount(){
this.textInput.focus();
}
render(){
return (
{ this.textInput = input }}>
)
}
}
复制代码
当我们在中添加
ref
属性时,其回调函数接收已经挂载的组件实例作为参数,并通过
this.textInput
访问到其内部的focus
方法。也就是说,上面的示例代码实现了当AutoFocusTextInput
组件挂载后组件的自动聚焦。
接下来文档指出,组件必须是使用
class
声明的组件,不然无法使用。这意味着React逐渐与ES6全面接轨了。
第三个重点,不能在无状态组件中使用`ref`。
原因很简单,因为ref
引用的是组件的实例,而无状态组件准确的说是个函数组件(Functional Component),没有实例。上代码:
function MyFunctionalComponent() {
return ;
}
class Parent extends React.Component {
render() {
return (
{ this.textInput = input; }} />
);
}
}
复制代码
上面的代码是无法正常工作的。
第四个重点,父组件的ref回调函数可以使用子组件的DOM。
这是Facebook非常不推荐的做法,因为这样会打破组件的封装性,这种方法只是某些特殊场景下的权宜之计。我们看看如何实现,上代码:
function CustomTextInput(props) {
return (
);
}
class Parent extends React.Component {
render() {
return (
this.inputElement = el}
/>
);
}
}
复制代码
原理就是父组件把ref
的回调函数当做inputRef
props传递给子组件,然后子组件
把这个函数和当前的DOM绑定,最终的结果是父组件
的this.inputElement
存储的DOM是子组件
中的input
。
同样的道理,如果A组件是B组件的父组件,B组件是C组件的父组件,那么可用上面的方法,让A组件拿到C组件的DOM。但是官方态度是discouraged,这种多级调用确实不雅,我们确实需要考虑其他更好的方案了。
结语:
`refs`提供的是另一种与react传统响应数据流完全不同的组件间交互方式,所以官方指出不要过度使用`refs`,而且从官方对它的态度来看,未来或许有更好的API来取代它。但目前来说`refs`仍是一个不错的解决方案。最近社区对于React的改进建议越来越多,例如this.setState()
这样的回调函数到底是不是一个好方法,对于复杂程度高,数量多的组件如何高效地进行单元测试,大型应用对于大量state如何进行有效的管理,虽然有redux,mobx这样优秀的解决方案,但如果react从根本设计上解决这一痛点,是否能再次对前端开发进行新一轮技术革命呢?
今天是2017年5月31日,四年前的5月30日,React正式发布了。过去的四年是web技术发展最快的四年,无数新技术和新思想喷薄而出。下个四年,我们共同期待。