上一篇(Semantic-UI的React实现(三):基本元素组件)已经提到过,基本元素组件的实现由于没有复杂的交互,仅仅是CSS类的编辑和组装,所以实现原理相对比较简单。html
但简单的东西要想作的简洁,每每不简单。react
想要简洁高效地封装数十个基本组件,将组件的相同处理部分抽象出来是很是必要的。在ES6中js新增了class关键字(固然这只是一个语法糖,其背后的处理原理仍然是prototype那一套东西。),有了这个关键字js在抽象与封装的思想上比以前更进了一步。segmentfault
当用“继承”的思想去考虑问题后,组件的共通处理很明显能够经过继承一个共同父类来完成(一般我更愿意用接口而非继承,无奈js学艺不精,不清楚接口继承如何实现)。继承之后,全部基本组件的如下处理,都可以由父类的处理完成:this
编辑和组装CSS类spa
渲染组件自己prototype
封装事件系统的方法回调code
在系列文章二的时候有提到过,基本组件的CSS编辑和组装,在PropsHelper中实现,全部细节对外隐藏,组件仅需声明相关属性便可。如Header使用到的属性:htm
// 属性定义 const PROP_TYPES = PropsHelper.getDefaultPropTypes().concat([ 'size', 'sub', 'dividing', 'floated', 'aligned', 'inverted', 'inline', 'color' ]);
这些可用属性的声明,再加上Button组件实例的props,便可编辑和组装出所需的CSS类名集合。在Header的render方法中,仅需调用:blog
render() { // 渲染元素 let style = this.createElementStyle(this.props, PROP_TYPES) + ' header'; return super.render(style); }
具体的生成style的细节,在Header的父类UiElement中:继承
/** * 生成元素的style */ createElementStyle(props, propsDef) { ... return PropsHelper.createStyle(props, propsDef) + ' ' + style; }
渲染组件也是共通处理实现的,做为子类的基本组件,仅需调用super.render便可:
render(style, children, props) { return React.createElement( this.props.as, // 组件的html标签(默认div) { id: this.props.id, // 组件ID className: style, // 组件class ...this.getEventCallback(), // 事件回调声明 ...props // 组件其余props(用于生成class的props不须要了) }, children ? children : this.props.children ); }
最开始的时候,其实并无这个实现,各个组件的渲染过程仍是留在组件各自的render中的。但随着组件的增多,发现这部分代码可重用性很是大。若是有特殊的组件不适用这个过程,直接在该组件中覆写该方法便可。这对总体代码的可维护性也有很大程度的提升。
这个功能目前还在实现中。个人目标是,任何组件仅需声明而无需在该组件内部实现回调,由公共方法来实现回调处理。如一个Button想要用onClick方法,直接声明:
<Button onClick={this.handleClick}>Btn</Button>
但在Button组件内部无需实现onClick的回调处理。(实际上也没法实现,由于Button的render处理是在其父类UiElement中实现的)
const EVENT_CALLBACK = [ 'onKeyDown', 'onKeyPress', 'onKeyUp', 'onFocus', 'onBlur', 'onChange', 'onInput', 'onSubmit', 'onClick', 'onContextMenu', 'onDoubleClick', 'onDrag', 'onDragEnd', 'onDragEnter', 'onDragExit', 'onDragLeave', 'onDragOver', 'onDragStart', 'onDrop', 'onMouseDown', 'onMouseEnter', 'onMouseLeave', 'onMouseMove', 'onMouseOut', 'onMouseOver', 'onMouseUp', 'onSelect', 'onTouchCancel', 'onTouchEnd', 'onTouchMove', 'onTouchStart', 'onScroll', 'onWheel', 'onLoad', 'onError', 'onTransitionEnd', 'onAnimationStart', 'onAnimationEnd', 'onAnimationIteration', ];
对于事件系统的回调,在constructor中是这样定义的:
constructor(props) { super(props); let eventProps = {}; for (let key in props) { if (key.indexOf('on') == 0 && EVENT_CALLBACK.indexOf(key) >= 0) { eventProps[key] = this.handleCallback.bind(this, key); } } this.eventCallbacks = eventProps; }
这个组件传入的props中若是包含'onXXX'而且这个'onXXX'在EVENT_CALLBACK中有定义,则认为该组件声明了一个事件系统的回调,那么UiElement将绑定这个回调的具体处理。处理过程如此实现:
handleCallback(callback, e) { if (this.props.callback) { this.props.callback(e); } }
在UiElement中,实现了三类公共功能供基本组件类调用:
编辑和组装CSS类
渲染组件自己
封装事件系统的方法回调
实现之后,基本组件类的相同处理均被抽离出来,仅剩下一些声明性质的代码。例如Header组件的实现被简化为:
import React from 'react'; import PropsHelper from './PropsHelper'; import UiElement from './UiElement'; // 属性定义 const PROP_TYPES = PropsHelper.getDefaultPropTypes().concat([ 'size', 'sub', 'dividing', 'floated', 'aligned', 'inverted', 'inline', 'color' ]); /** * 标题组件 */ class Header extends UiElement { // 类型定义 static propTypes = { ...PropsHelper.createPropTypes(PROP_TYPES) }; // 默认值定义 static defaultProps = { ...PropsHelper.getDefaultPropsValue(PROP_TYPES) }; /** * 取得渲染内容 */ render() { // 渲染元素 let style = this.createElementStyle(this.props, PROP_TYPES) + ' header'; return super.render(style); } } export default Header;
这样的好处是显而易见的:
简化实现代码提升可阅读性
封装共通处理提升可维护性
经过方法覆写保持可扩展性
经过这几篇,基础组件的封装处理应该说完了,接下来的几篇打算说说复杂组件的实现。在完成全部组件的封装后,还打算扩展一些复杂组件的功能(代码丑,只能多实现些功能了。总之要和官方作成不同的/(ㄒoㄒ)/~~)。