带着三个问题深刻浅出React高阶组件

前言

"高阶"二字听起来很是唬人,由于大学高数课上的高阶方程让人抓狂,从而让第一次接触"高阶组件"概念的人们误觉得又是什么高深的思想和复杂的逻辑。但相信在你学习完成后和生产环境大量使用过程当中,就会发现这个所谓"高阶组件"真的一点也不高阶,很是简单易懂。本文经过回答三个问题带你深刻浅出React高阶组件。vue

1.为何须要高阶组件?

2.高阶组件是什么?

3.如何实现高阶组件?


1.为何须要高阶组件?

这个问题很简单,为何咱们须要react/vue/angular?使用框架最核心的缘由之一就是提升开发效率,能早点下班。同理,react高阶组件可以让咱们写出更易于维护的react代码,能再早点下班~react

举个栗子,ES6支持使用import/export的方式拆分代码功能和模块,避免一份文件里面出现"成坨"的代码。同理对于复杂的react组件,若是这个组件有几十个自定义的功能函数,天然要进行拆分,否则又成了"一坨"组件,那么该如何优雅地拆分组件呢?react高阶组件应运而生。chrome

在使用ES5编写react代码时,可使用Mixin这一传统模式进行拆分。新版本的react全面支持ES6并提倡使用ES6编写jsx,同时取消了Mixin。所以高阶组件愈来愈受到开源社区的重视,例如redux等知名第三方库都大量使用了高阶组件。redux

2.高阶组件是什么?

回答这个问题前,咱们先看下本文的封面图,为何笔者用初中生就掌握的一元一次函数来表明这篇文章呢?很显然,高阶函数就是形如y=kx+b的东西,x是咱们想要改造的原组件,y就是改造事后输出的组件。那具体是怎么改造的呢?kb就是改造的方法。这就是高阶组件的基本原理,是否是一点也不高阶~bash

再举个栗子相信更能让你明白:咱们写代码须要进行加法计算,因而咱们把加法计算的方法单独抽出来写成一个加法函数,这个加法函数能够在各处调用使用,从而减小了工做量和代码量。而咱们独立出来的这个能够随处使用的加法函数,类比地放在react里,就是高阶组件。app

3.如何实现高阶组件?

从上面的问题回答中,咱们知道了:高阶组件其实就是处理react组件的函数。那么咱们如何实现一个高阶组件?有两种方法:框架

1.属性代理 2.反向继承函数

第一种方法属性代理是最多见的实现方式,将被处理组件的props和新的props一块儿传递给新组件,代码以下:学习

//WrappedComponent为被处理组件
function HOC(WrappedComponent){
    return class HOC extends Component {
        render(){
            const newProps = {type:'HOC'};
            return <div>
                <WrappedComponent {...this.props} {...newProps}/>
            </div>
        }
    }
}

@HOC
class OriginComponent extends Component {
    render(){
        return <div>这是原始组件</div>
    }
}

//const newComponent = HOC(OriginComponent)
复制代码

属性代理听起来好像很麻烦,然而从代码中看,就是使用HOC这个函数,向被处理的组件WrappedComponent上面添加一些属性,并返回一个包含原组件的新组件。从chrome调试台上咱们能够看到原始组件已经被包裹起来了并具备type属性:ui

上述代码使用了ES7的decorator装饰器来实现对OriginComponent组件的装饰和加强,或者使用注释中的函数方法同样能够达到相同的效果。

使用属性代理的好处就是,能够把经常使用的方法独立出来并屡次复用。好比咱们实现了一个加法函数,那么咱们把加法函数改形成形如上述HOC函数的形式,以后对其余组件进行包裹,就能够在组件里使用这个方法了。

第二种方法反向继承就有意思了,先看代码:

function HOC(WrappedComponent){
    return class HOC extends WrappedComponent {
        //继承了传入的组件
        test1(){
            return this.test2() + 5;
        }
        
        componentDidMount(){
            console.log('1');
            this.setState({number:2});
        }

        render(){
            //使用super调用传入组件的render方法
            return super.render();
        }
    }
}

@HOC
class OriginComponent extends Component {
    constructor(props){
        super(props);
        this.state = {number:1}
    }
    
    test2(){
        return 4;
    }
    componentDidMount(){
        console.log('2');
    }

    render(){
        return (
            <div>
                {this.state.number}{'and'}
                {this.test1()}
                这是原始组件
            </div>
        )
    }
}

//const newComponent = HOC(OriginComponent)
复制代码

代码看完咱们可能还有点懵,那咱们先来剖析关键词"继承"。何谓继承?新生成的HOC组件经过extends关键字,得到了传入组件OriginComponent全部的属性和方法,是谓"继承"。也就是说继承以后,HOC组件可以实现OriginComponent组件的所有功能,并且,HOC能够拿到stateprops进行修改,从而改变组件的行为,也就是所谓的"渲染劫持"。能够说,经过反向继承方法实现的高阶组件相比于属性代理实现的高阶组件,功能更强大,个性化程度更高,适应更多的场景。

如上的代码,咱们能够看到:

第一:this.test1()输出了9。为何? 由于在ES6中,super做为对象调用父类方法时,super绑定子类的this。故执行super.render()OriginComponent中的this指向的是HOC组件,因此可以成功地执行test1函数。

第二:控制台输出的是1而不是2。为何? 首先,decorator是在代码编译阶段执行,故HOCrender方法在OriginComponentrender方法以前执行。而且子组件HOC是继承于父组件OriginComponent,二者具备继承关系HOC.__proto__ === OriginComponent,当执行componentDidMount方法时,子组件已存在该方法,故执行完毕后结束,再也不根据__proto__向上继续寻找。若是咱们将子组件HOC中的componentDidMount方法去掉,那么控制台将输出2。

当咱们有多个高阶组件须要同时加强一个组件时该怎么办呢?咱们能够这样写:

@fun1
@fun2
@fun3
class OriginComponent extends Component {
    ...
}
复制代码

也可使用lodashflowRight方法:

const enchance = lodash.flowRight(fun1,fun2,fun3);

@enchance
class OriginComponent extends Component {
    ...
}
复制代码

由于fun1 fun2 fun3都是处理类的函数,只要实现按顺序依次对类进行处理便可。

以上就是关于高阶组件实现方式的所有内容。为了查缺补漏,官方文档中有两条建议很中肯,在这里摘抄给你们:

一句话总结,为了不在调试时,由于高阶组件的存在而致使满屏的HOC(以上述代码为例),能够设置类的displayName属性修改组件的名称。

若是你对ES6中的继承很是了解的话,那理解上述文字应该很是简单。ES6的继承中,子类不能继承父类的静态方法,即便用static关键字定义的方法。若是子类想使用,那么必定要copy以后才能使用。

总结一下,高阶组件其实就是处理组件的函数,他有两种实现方式: 一是属性代理,相似于一元一次方程的y = x + b,输入x是原组件,参数b是你要添加或删除的属性/方法,y是最终输出的组件。 二是反向继承,相似于一元一次方程的y = kx + b,能够拿到stateprops进行渲染劫持(k),改变组件的行为。

相关文章
相关标签/搜索