React组件设计之高阶函数和插件机制

作者简介:slashhuang 研究型程序员 现就职于爱屋吉屋

React技术栈已成为大部分互联网公司的标配。关于React组件设计,大家经常谈的是高阶组件、props等等,市面上关于组件设计的文章也相对较少。本文笔者将从高阶组件和插件设计的角度,阐述在React项目中个人的一些组件设计心得。

一个基本的React组件

我们从简单的代码着手,进行React组件的讨论。

import { Component,PropTypes },React from 'react';import {render} from 'react-dom';class FirstComponent extends Component{    constructor(){        super();        this.state={ text: 'hello world' }    }    //描述数据类型     static propTypes={        velocity: PropTypes.number,//滚动速度    }    //描述默认的props    static defaultProps={        velocity:500    }    clickFunc(){        this.setState({text:'I am clicked'})    }    render(){        return <div onClick={::this.clickFunc}>                 {this.state.text}               </div>    } };

一个信息完备的React组件,一般都具备上述这种结构,这样的结构具备以下几个功能点。

1.采用propTypes来描述组件props的数据类型和含义。2.采用this.state来描述组件内部的数据结构。

这样的一个组件已经能够覆盖业务层面的大部分功能。

它的不足之处在于太不灵活。别的开发者必须通过修改源码的形式增加组件功能。

如果这个组件被多处复用,那么修改源码将会是一件危险的事情。

那么问题来了,怎么在不修改源码的基础上为组件增加功能呢?

下面我们从高阶组件和插件机制来增加组件的灵活性。

高阶组件HOC丰富组件功能

HOC的简单定义

高阶组件的概念来自于高阶函数,一般指的是将ReactComponent 作为参数,同时,函数的return值也为ReactComponent的转换模式。

一个基本的高阶组件写法如下

const HOC = (嵌入逻辑)=>(目标组件)=>{    return 增加功能后的新组件}

HOC的第一个参数是我们要嵌入的逻辑,目标组件则是我们要改造的组件,最后这个HOC返回出来一个增加功能后的新组件,这个新组件就是在目标组件的基础上修改过功能的组件。

接下来,我们采用如上HOC的逻辑来动态修改React组件的内部方法、props和state。

引入HOC来修改React组件内部方法

为了表达更加直观,我们来实现一个具体的业务场景。

我们定义如下高阶函数fn,使得InnerComponent目标组件在每次click后都能在控制台打印日志。

HOC = hookFn=>InnerComponent=>newComponent 为了侵入InnerComponent的逻辑,我们需要在原来InnerComponent.prototype的基础上,嵌入hookFn的逻辑。

const highOrderFunc=hookFn=>InnerComponent=>{       //引用目标组件原型        let ref = InnerComponent.prototype;        let cache = ref['clickFunc'];        //修改原型,hook我们自定义的功能        ref['clickFunc']=function(...args){            cache.apply(this,args)            hookFn()        }        return InnerComponent}

如上,我们就在保持原来 InnerComponent.prototype['clickFunc']方法逻辑的基础上,增加了hookFn的逻辑。比如我定义hookFn = console.log('clicked'),就可以实时记录用户的点击事件。

下面我们将这个简单逻辑完整组装起来。

@highOrderFunc(()=>console.log('hook click called'))class FirstComponent extends Component{    constructor(){        super();        this.state={ text: 'hello world' }    }    clickFunc(){        this.setState({text:'I am clicked'})    }    render(){        return <div onClick={::this.clickFunc}>                  {this.state.text}               </div>    } };render(<FirstComponent />,document.getElementById('root'))

当我们做了如上操作后,点击FirstComponent的时候,即可在控制台打印hook click called。

如果我们见微知著,将hookFn逻辑改成一段前端打点,即可实现产品经理经常要求的打点功能,并且对原来的FirstComponent逻辑没有任何侵入。

关于如上的代码需要说明的是,@符号是ES7的decorator语法,在高阶组件中使用会显得比较简洁,这里不多做介绍。

如上例子演示的是HOC通过修改组件的prototype,来实现对事件逻辑的侵入。

下面我们继续写代码,采用HOC来实现对组件props和state的侵入。

引入HOC修改state和props

同样,为了表达直观,我们来实现react-redux中,通过connect将action挂载在props上的逻辑。

我们定义如下高阶组件,使得newComponent的this.props能够访问actions。

HOC = actions=>InnerComponent=>newComponent 为了侵入InnerComponent的this.props,我们需要将InnerComponent包裹一层,以便在render的时候,this.props上能够拿到actions。

我们定义一个Wrapper组件来包裹InnerComponent,并且在Wrapper的componentDidMount时机,修改InnerComponent.prototype来完全覆盖InnerComponent原来的click逻辑。

    class Wrapper extends Component{        componentDidMount(){             let _ref = this.refs.InnerComponent;             //覆盖原来组件的click逻辑             _ref.__proto__.clickFunc = function(...args){                this.props.Update();                let { text } = this.state;                this.setState({text:`add Text ${text}`})             }        }        render(){            //侵入props数据            this.props = {                ...this.props,                ...actions            };            return <InnerComponent ref='InnerComponent'                                  {...this.props}/>        }    }    return Wrapper;}

如上的HOC返回的Wrapper组件在UI展示上和InnerComponent一模一样。同时,Wrapper在render的时候,this.props动态添加了actions传入InnerComponent。最后,InnerComponent的click逻辑clickFunc也被覆盖,因而在click的时候可以执行this.props.Update()逻辑。

@HOC({Update:()=>console.log('Update')})class InnerComponent extends Component{    constructor(){        super()        this.state={ text: 'hello world' }    }    clickFunc(){        this.setState({text:'I am clicked'})    }    render(){        return <div onClick={()=>this.clickFunc()}>                {this.state.text}</div>    } };

如上即为第二个例子的完整演示。我们通过高阶组件HOC实现了对InnerComponent的事件及props侵入。事实上,第二个例子的实现已经非常类似react-redux中的connect的功能了。

阶段性小结 HOC的核心思路是夺取目标组件的控制权,将逻辑、props、state修改交给HOC。 目标组件的控制权转移给HOC是它的核心。 讲完HOC,接下来我们从props设计的角度来审视React组件设计

由于在前端开发中,UI改版是一个经常碰到的需求。因此,React组件设计需要兼顾功能和UI侵入。我们通过定义Plugins接口,来定制可拔插的插件体系。

同样,为了表达直观,我们来实现一个Slider中底部文案的样式修改。

定义Plugins接口实现插件体系

class Slider extends Component{    renderPlugins(){        let { Plugins } = this.props;        let dataModel = {...this.props,...this.state};        return do{            if(typeof Plugins=='function'){;                <Plugins  dataModel={dataModel}/>            }else{                Plugins;            }        }    }    render(){        return <div>                hello world                {Plugins && this.renderPlugins() }            </div>    }};

如上,给Slider组件默认提供一个Plugins接口,这个Plugins的props是Slider的props和state数据集合。这样的一个模型即可完成当Slider的props更新、click事件发生的时候,Plugins能够拿到所有的数据,从而完成plugins层面的UI更新。

这种机制重要的一点是对Slider组件原来的逻辑无侵入。

当开发者需要修正UI样式的时候,直接定义Plugins即可完成这个工作。

关于如上的代码需要说明的是,代码中的do expression是babel-stage-0的语法,对于React组件中的条件分支处理非常直观。

结语

这篇文章洋洋洒洒都写了快200行了,感谢大家能够读到这里。关于React的组件设计,这边主要是采用高阶组件和Plugin机制来实现动态性。

原文发布于微信公众号 - 前端黑板报(FeHeiBanBao)

原文发表时间:2017-03-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员的诗和远方

写个小编辑器娱乐自己

上周有两天感冒低烧了,躺在床上昏昏沉沉听罗胖的发布会,说到《黑天鹅》书中的一句话:现代社会给我们的双重惩罚是,既让我们衰老的更快,又让我们活得更长。深有同感,...

34470
来自专栏游戏杂谈

webgame开发中常用的一些类

1、加载队列QueueLoader,游戏一定是有很多的资源需要加载,这个类可以让资源以顺序进行加载;

8520
来自专栏web编程技术分享

【手把手】JavaWeb 入门级项目实战 -- 文章发布系统 (第十节)1. 详情页面的布局2. 从主页面到详情页面的跳转问题

36240
来自专栏做全栈攻城狮

小白学编程实战项目-利用Winform开发美女音乐播放器

这是小白学习软件开发系列课程,旨在帮助对电脑编程感兴趣的朋友学习并熟悉C#技术。其中基础部分已经讲解完毕,可以查看:电脑编程入门(10)-C#面向对象编程浅聊,...

26830
来自专栏nice_每一天

一天带你入门到放弃vue.js(一)

每个新的框架入手都会进行一些列的扯犊子!这里不多说那么多!简简单单说一下vue吧!

19220
来自专栏企鹅号快讯

前端工程师面试题汇总

作者:@markyun markyun.github.io/2015/Front-end-Developer-Questions/ HTML Doctype作用...

30880
来自专栏李蔚蓬的专栏

10.1.5 布局优化利器之 Hierarchy Viewer

无论是哪本讲解布局优化的参考书,它们都不得不提到Hierarchy Viewer。不过,通常情况下,Hierarchy( 英['haɪərɑːkɪ])Viewe...

9630
来自专栏编程微刊

仿百度排列图片预览插件-Simple Lightbox

很久以前遇到过这样的一个面试题,要求手写代码,实现百度图片的排列预览,并且可以左右点击查看下一张照片,当时没有做出来,这个问题也就一直放在了脑后,工作之后,遇到...

23520
来自专栏大数据钻研

前端面试那些坑

HTML Doctype作用?严格模式与混杂模式如何区分?它们有何意义? HTML5 为什么只需要写 ? 行内元素有哪些?块级元素有哪些? 空(void)元素有...

35760
来自专栏iOS技术

iOS图片浏览器(功能强大/性能优越)

支持 cocopods,功能完善,性能不错,代码质量尚可,喜欢的朋友可以给个小星星?。

59370

扫码关注云+社区

领取腾讯云代金券