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机制来实现动态性。
- 全自动驾驶,吹牛容易实现难!有90%的人都不了解这些细节
- .NET4.0下web应用程序用UrlRewriter.dll重写无后缀路径
- Silverlight中摄像头的运用—part2
- 区块链小白投资入门操作指南(上)
- 《我的WCF之旅》博文系列汇总
- 网站出现“Service Unavailable”提示该如何解决
- Silverlight 4 中摄像头的运用—part1
- Silverlight 4 中摄像头的运用—part1
- Silverlight 4 中数据绑定发生的变化
- 未来3年 人工智能如何影响法律行业?5位权威专家给出趋势
- silverlight.js详解.
- 前端三大框架vue,angular,react大杂烩
- Silverlight制作scrollbar.
- [Silverlight动画]转向行为 - 躲避行为
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- PySCF程序包平均场计算的一些收敛技巧
- 你应该知道的10个Python文件系统方法
- 适合初学者的Python装饰器的简易教程
- 一起刷Leetcode第一篇,数组和字典的妙用
- 加速Python列表和字典,让你代码更加高效
- 如何使用Python的Flask和谷歌app Engine来构建一个web app
- 如何用Python实现电子邮件的自动化
- 在Win下安装Visual Studio和Parallel Studio XE
- 我们将项目语言从Python转向Go的5个原因
- GFN-xTB的编译与API使用
- 红外光谱的理论计算
- 一起刷题(leetcode)第二篇:如何用Python实现递归
- 如何成为Python的数据操作库Pandas的专家?
- 谈谈Gaussian软件中的guess=mix
- 用ORCA做DLPNO-CCSD(T)计算