从React官方文档看 refs 的使用和将来

React先进的开发思想一直为社区所称道,基于数据流的设计极大地简化了前端开发成本。但如同官方文档所述,web开发中有不少场景需求是脱离数据流的,典型的如处理文本输入框聚焦(focus)。为此React提供了refs供开发者使用。前端

笔者是在2015年下半年开始学习React,那时候官方文档关于refs的介绍和使用与如今彻底不一样。最新的React版本(v15.5.4)已经对这个API进行了修改并更新,不过咱们仍能够在文档中找到老版本API的蛛丝马迹:react

咱们来比较下新老refs有哪些异同:git

在旧版本中,如上图所述,refs的使用很是简单,由于每一个组件实例都有一个this.refs属性,会自动引用全部包含ref属性组件的DOM,因此咱们只须要在目标组件上添加一个自定义的ref,而后进行使用便可:github

class Button extends Component {
    constructor(props){
        super(props);
    }

    componentDidMount = () => {
        let btn = this.refs.btn;
        let link = this.refs.link;
    }
    
    render(){
        return (
            <div>
                <button ref='btn'>click</button>
                <a href='facebook.github.io/react' ref='link'>click</a>
            </div>
        )
    }
}
复制代码

文档加粗部分提醒到,将会在将来的某个版本把这种用法彻底移除掉,建议开发者升级版本后使用新的ref。那么新的ref如何使用呢?web

第一个重点是将ref改成回调函数的方式去使用。

直接上代码:redux

class Input extends Component {
    constructor(props){
        super(props);
    }
    
    focus = () => {
        this.textInput.focus();
    }
    
    render(){
        return (
            <div>
                <input ref={(input) => { this.textInput = input }} />
            </div>
        )
    }
}
复制代码

这里咱们可能就有第一个疑问了,input参数是哪来的?文档中这样解释:数组

这就说明,当咱们在DOM Element中使用ref时,回调函数将接收当前的DOM元素做为参数,而后存储一个指向这个DOM元素的引用。那么在示例代码中,咱们已经把input元素存储在了this.textInput中,在focus函数中直接使用原生DOM API实现focus聚焦。bash

那么第二个疑问出现了,回调函数何时被调用?函数

答案是当组件挂载后和卸载后,以及ref属性自己发生变化时,回调函数就会被调用。单元测试

第二个重点是,能够在组件实例中使用`ref`。

前面的示例代码是在DOM添加ref属性,那么咱们来看看如何在组件实例中使用。再上代码:

//<Input>来源于上面的示例代码👆
class AutoFocusTextInput extends Component {
    componentDidMount(){
        this.textInput.focus();
    }
    
    render(){
        return (
            <Input ref={(input) => { this.textInput = input }}>
        )
    }
}
复制代码

当咱们在<Input>中添加ref属性时,其回调函数接收已经挂载的组件实例<Input>做为参数,并经过this.textInput访问到其内部的focus方法。也就是说,上面的示例代码实现了当AutoFocusTextInput组件挂载后<Input>组件的自动聚焦。

接下来文档指出,<Input>组件必须是使用class声明的组件,否则没法使用。这意味着React逐渐与ES6全面接轨了。

第三个重点,不能在无状态组件中使用`ref`。

缘由很简单,由于ref引用的是组件的实例,而无状态组件准确的说是个函数组件(Functional Component),没有实例。上代码:

function MyFunctionalComponent() {
    return <input />;
}

class Parent extends React.Component {
    render() {
        return (
            <MyFunctionalComponent
                ref={(input) => { this.textInput = input; }} />
        );
    }
}
复制代码

上面的代码是没法正常工做的。

第四个重点,父组件的ref回调函数可使用子组件的DOM。

这是Facebook很是不推荐的作法,由于这样会打破组件的封装性,这种方法只是某些特殊场景下的权宜之计。咱们看看如何实现,上代码:

function CustomTextInput(props) {
    return (
        <div>
            <input ref={props.inputRef} />
        </div>
    );
}

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

原理就是父组件把ref的回调函数当作inputRefprops传递给子组件,而后子组件<CustomTextInput>把这个函数和当前的DOM绑定,最终的结果是父组件<Parent>this.inputElement存储的DOM是子组件<CustomTextInput>中的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技术发展最快的四年,无数新技术和新思想喷薄而出。下个四年,咱们共同期待。

相关文章
相关标签/搜索