美文网首页React
React 高阶组件

React 高阶组件

作者: 柏丘君 | 来源:发表于2017-06-02 18:02 被阅读111次

    高阶组件是对既有组件进行包装,以增强既有组件的功能。其核心实现是一个无状态组件(函数),接收另一个组件作为参数,然后返回一个新的功能增强的组件。
    看一个最简单的例子:
    App.js:

    import React,{ PureComponent } from "react";
    
    const DisplayUser = (props) => {
        return(
            <div>
                姓名:{ props.username }
            </div>
        );
    } 
    
    const HOC = (InnerComponent) => {
        return class HOCComponent extends PureComponent{
            render(){
                const { username } = this.props;
                return(
                    <InnerComponent username = { username } />
                );
            }
        }
    }
    
    export default HOC(DisplayUser);
    

    index.js:

    import React from "react";
    import ReactDOM from "react-dom";
    import HOC from "./App";
    
    ReactDOM.render(<HOC username = "Mike" birth = "1999-09-09" />,document.getElementById("root"));
    

    HOC 函数接受一个组件作为参数,返回一个新的组件,新组件中渲染了参数组件,这样就可以对参数组件做一些配置,例如本例的过滤 props,只传递给参数组件合法的 props,不传递非法的 props。

    高阶组件的分类

    高阶组件分为两种类型:

    • 代理型高阶组件
    • 继承型高阶组件

    上面的例子中的高阶组件类型就是代理型高阶组件。
    二者的主要区别是对参数组件的操作上,代理型高阶组件是返回一个新的组件类,该类继承于根组件(Component 或 PureComponent),然后让这个新的组件类去渲染参数组件,这样就可以对参数组件实现包装或代理 props 的操作。继承型高阶组件也是返回一个新的组件类,但这个新的类不继承根组件类,而是继承于参数组件类,这样我们就可以重写参数组件类中的一些方法。
    这里的高阶组件应该叫做高阶组件生成函数,其执行的返回值才叫高阶组件,为了方便我们这里统一称为高阶组件了,请知悉。
    下面分别来说下这两类的高阶组件。

    代理型高阶组件

    代理型高阶组件接受一个组件作为参数,返回一个新的继承于根组件(Component、PureComponent)的组件,并用该新组件渲染参数组件,其基本结构为:

    export const HOC = (SomeComponent) => {
        return class HOCComponent extends PureComponent{
            render(){
                doSomeThing()...
                return(
                    <SomeComponent />
                );
            }
        }
    }
    

    代理型高阶组件有以下几个作用:

    • 代理 props
    • 访问 ref
    • 抽取状态
    • 包装组件

    代理 props

    代理型高阶组件可以接收被包装组件的 props,并对这些 props 进行操作,如增删改操作。在渲染被包装组件时,传入被修改的 props,如本文开头的例子。
    基本结构如下:

    export const HOC = (SomeComponent) => {
        return class HOCComponent extends PureComponent{
            render(){
                const newProps = doSomeThing(this.props);
                return(
                    <SomeComponent {...newProps} />
                );
            }
        }
    }
    

    访问 ref

    ref 是一个特殊的属性,可以声明为函数,如果 ref 属性是一个函数,那么将在组件装载完成后自动执行该函数,并将当前组件的实例传入该函数中
    看一个栗子:

    class SubComponent extends PureComponent{
        render(){
            return(
                <div className = "box">
                    我想要被获取!!!
                </div>
            );
        }
    }
    
    const HOC = (InnerComponent) => {
        return class HOCComponent extends PureComponent{
            constructor(...args){
                super(...args);
                this.handSubRef = this.handSubRef.bind(this);
            }
            
            // 获取子组件的实例
            // 该函数在子组件被装载后自动调用
            handSubRef(subEle){
                console.log(subEle);
            }
    
            render(){
                return(
                    <InnerComponent ref = { this.handSubRef } />
                );
            }
        }
    }
    
    export default HOC(SubComponent);
    

    这样,在参数组件被装载后,就会自动执行 handSubRef 函数,并将参数组件的实例传入 handSubRef 函数。在 handSubRef 中可以获取参数组件的一些列属性,如 state、props 等。
    注意,如果参数组件是一个无状态组件,那么传入 handSubRef 的参数将是 null

    const SubComponent = (props) => {
        return(
            <div className = "box">
                我想要被获取!!!
            </div>
        );
    } 
    
    const HOC = (InnerComponent) => {
        return class HOCComponent extends PureComponent{
            constructor(...args){
                super(...args);
                this.handSubRef = this.handSubRef.bind(this);
            }
            
            // 参数组件是无状态组件
            // subEle 为 null
            handSubRef(subEle){
                console.log(subEle);
            }
    
            render(){
                return(
                    <InnerComponent ref = { this.handSubRef } />
                );
            }
        }
    }
    
    export default HOC(SubComponent);
    

    抽取状态

    当一个组件的功能较复杂时,我们建议将组件拆分为容器组件和UI组件,UI组件负责展示,容器组件用来进行逻辑管理。这个步骤就是“抽取状态”。
    我们将前面的计算器应用中的组件进行一些修改:

    ...
    const UICounter = (props) => {
        const { increase,decrease,value} = props;
        return(
            <div className = "counter">
                <div>
                    <button onClick = { increase }>+</button>
                    <button onClick = { decrease }>-</button>
                </div>
                <span>当前的值为:{ value }</span>
            </div>
        );
    }
    
    const HOC = (SubEle) => {
        return class HOCComponent extends PureComponent{
            constructor(props) {
                super(props);
                // 获取初始状态
                this.state = {
                    value:store.getState().value,
                };
            }
    
            componentWillMount() {
                // 监听 store 变化
                store.subscribe(this.watchStore.bind(this));
            }
    
            componentWillUnmount() {
                // 对 store 变化取消监听
                store.unsubscribe(this.watchStore.bind(this));
            }
    
            // 监听回调函数,当 store 变化后执行
            watchStore(){
                // 回调函数中重新设置状态
                this.setState(this.getCurrentState());
            }
    
            // 从 store 中获取状态
            getCurrentState(){
                return{
                    value:store.getState().value,
                }
            }
    
            // 增加函数
            increase(){
                // 派发 INCREMENT Action
                store.dispatch(ACTIONS.increament());
            }
    
            // 减少函数
            decrease(){
                // 派发 DECREAMENT Action
                store.dispatch(ACTIONS.decreament());
            }
    
            render(){
                return(
                    <UICounter
                        increase = { this.increase.bind(this)}
                        decrease = { this.decrease.bind(this)}
                        value = { this.state.value }
                    />
                );
            }
        }
    }
    
    export default HOC(UICounter);
    ...
    

    包装组件

    包装组件就是对某些组件进行组合,返回不同的组合形式,同时可以设置展示样式。如将展示性 select 和输入表单包装成可搜索的 select等。
    基本结构如下:

    // 可以传入一个数组,或者单个组件
    const HOC = (SubComponents) => {
        return class HOCComponent extends PureComponent{
            render(){
                const style = ...
                // 将子组件组合成一个大组件,同时可以修改样式
                return(
                    <div style = { style }>
                        {...SubComponents}
                    </div>
                );
            }
        }
    }
    
    export default HOC([Select,SearchBox]);
    

    至此,代理型高阶组件就说完了,下面说说继承型高阶组件。

    继承型高阶组件

    和代理型高阶组件创建一个继承于根组件(Component、PureComponent)的组件类不同的是,继承型高阶组件是创建一个继承于参数组件的组件类,以此可以覆写父组件中的一些方法。
    最常用的应用是重写父组件的生命周期函数:

    
    const HOC = (ParComponents) => {
        return class HOCComponent extends ParComponents{
            // 覆写父组件的生命周期函数
            shouldComponentUpdate(nextProps, nextState) {
                ...
            }
    
            render(){
                // 渲染时仍然按照父组件的 render 函数进行渲染
                return super.render();
            }
        }
    }
    
    export default HOC(ParComponents);
    

    子组件覆写了父组件的生命周期方法,以获取更好的渲染性能,在渲染(render)时,还是调用父类的 render 方法。

    函数作为子组件

    前面用高阶组件实现了代理 props 功能,对于特定的组件,需要传入特定的 props 名,由于每个组件需要的 props 可能有差异,如果使用高阶组件对参数组件进行判断,再传入合适的 props 名显然太麻烦了。这时如果我们需要更通用的方案,可以接收函数作为子组件,利用函数的形参的特性来排除组件 props 名之间的差异性。看一下演示代码:

    ...
    // 此组件需要使用 user props
    const SubComponent1 = (user) => {
        return(
            <TestComponent user = { user } />
        );
    } 
    
    // 此组件需要使用 username props
    const SubComponent2 = (user) => {
        return(
            <TestComponent2 username = { user } />
        );
    } 
    
    // 高阶组件接受 testUser 作为 props
    const HOC = (props) => {
        const { testUser } = props;
        return(
            props.children(testUser)
        );
    }
    ...
    

    使用此高阶组件传递 props:

    ...
    <HOC testUser = "MIKE">
        { SubComponent1 }
    </HOC>
    
    <HOC testUser = "JACK">
        { SubComponent2 }
    </HOC>
    ...
    

    高阶组件接收一个函数作为子组件,然后调用这个组件函数,并传入相应的 props,函数调用的结果返回一个组件,组件需要接收什么样的 props 名可以自行定义,不用在高阶组件中进行判断了。
    以函数作为组件的形式主要用于不同的组件需要接收同一份 props,但是它们需要的 props 名称不一样的情况,可以用函数的形参巧妙的化解掉父组件的判断。虽然用途较少,但不失为一个优秀的模式,将 children 进行调用简直是神来之笔有木有。

    总结

    本文谈到了 React 中的高阶组件,高阶组件主要包括两种类型:

    • 代理型高阶组件
    • 继承型高阶组件

    代理型高阶组件的主要用途是:

    • 代理子组件的 props(对 props 进行增删改操作)
    • 获取子组件的 ref(实例)
    • 抽取子组件状态(拆分逻辑)
    • 将子组件进行包装(组合子组件,调整样式等)

    继承型高阶组件的主要用途是覆写父组件的生命周期方法,通常是 shouldComponentUpdate 方法,以此来提高渲染性能,渲染时调用父类的 render 函数(super.render())。
    最后,介绍了函数作为子组件的情形,主要用于不同的组件接受同一份 props,但是它们各自需要的 props 名不同的情况,利用函数的形参巧妙化解父组件的额外判断操作。这是一种优秀的模式,让人耳目一新。

    完。

    相关文章

      网友评论

        本文标题:React 高阶组件

        本文链接:https://www.haomeiwen.com/subject/apbyfxtx.html