Semantic-UI的React實(shí)現(xiàn)(四):基本元素組件的共通處理(父類)實(shí)現(xiàn)
上一篇(Semantic-UI的React實(shí)現(xiàn)(三):基本元素組件)已經(jīng)提到過,基本元素組件的實(shí)現(xiàn)因?yàn)闆]有復(fù)雜的交互,僅僅是CSS類的編輯和組裝,因此實(shí)現(xiàn)原理相對比較簡單。
但簡單的東西要想做的簡潔,往往不簡單。
抽象與封裝
想要簡潔高效地封裝數(shù)十個基本組件,將組件的相同處理部分抽象出來是非常必要的。在ES6中js新增了class關(guān)鍵字(當(dāng)然這只是一個語法糖,其背后的處理原理仍然是prototype那一套東西。),有了這個關(guān)鍵字js在抽象與封裝的思想上比之前更進(jìn)了一步。
當(dāng)用“繼承”的思想去考慮問題后,組件的共通處理很明顯可以通過繼承一個共同父類來完成(通常我更愿意用接口而非繼承,無奈js學(xué)藝不精,不清楚接口繼承如何實(shí)現(xiàn))。繼承以后,所有基本組件的以下處理,均可以由父類的處理完成:
- 編輯和組裝CSS類
- 渲染組件本身
- 封裝事件系統(tǒng)的方法回調(diào)
實(shí)現(xiàn)細(xì)節(jié)
編輯和組裝CSS類
在系列文章二的時候有提到過,基本組件的CSS編輯和組裝,在PropsHelper中實(shí)現(xiàn),所有細(xì)節(jié)對外隱藏,組件僅需聲明相關(guān)屬性即可。如Header使用到的屬性:
- // 屬性定義
- const PROP_TYPES = PropsHelper.getDefaultPropTypes().concat([
- 'size', 'sub', 'dividing', 'floated', 'aligned', 'inverted', 'inline', 'color'
- ]);
這些可用屬性的聲明,再加上Button組件實(shí)例的props,即可編輯和組裝出所需的CSS類名集合。在Header的render方法中,僅需調(diào)用:
- render() {
- // 渲染元素
- let style = this.createElementStyle(this.props, PROP_TYPES) + ' header';
- return super.render(style);
- }
具體的生成style的細(xì)節(jié),在Header的父類UiElement中:
- /**
- * 生成元素的style
- */
- createElementStyle(props, propsDef) {
- ...
- return PropsHelper.createStyle(props, propsDef) + ' ' + style;
- }
渲染組件
渲染組件也是共通處理實(shí)現(xiàn)的,作為子類的基本組件,僅需調(diào)用super.render即可:
- render(style, children, props) {
- return React.createElement(
- this.props.as, // 組件的html標(biāo)簽(默認(rèn)div)
- {
- id: this.props.id, // 組件ID
- className: style, // 組件class
- ...this.getEventCallback(), // 事件回調(diào)聲明
- ...props // 組件其他props(用于生成class的props不需要了)
- },
- children ? children : this.props.children
- );
- }
最開始的時候,其實(shí)并沒有這個實(shí)現(xiàn),各個組件的渲染過程還是留在組件各自的render中的。但隨著組件的增多,發(fā)現(xiàn)這部分代碼可重用性非常大。如果有特殊的組件不適用這個過程,直接在該組件中覆寫該方法即可。這對整體代碼的可維護(hù)性也有很大程度的提高。
事件系統(tǒng)的回調(diào)
這個功能目前還在實(shí)現(xiàn)中。我的目標(biāo)是,任何組件僅需聲明而無需在該組件內(nèi)部實(shí)現(xiàn)回調(diào),由公共方法來實(shí)現(xiàn)回調(diào)處理。如一個Button想要用onClick方法,直接聲明:
- <Button onClick={this.handleClick}>Btn</Button>
但在Button組件內(nèi)部無需實(shí)現(xiàn)onClick的回調(diào)處理。(實(shí)際上也無法實(shí)現(xiàn),因?yàn)锽utton的render處理是在其父類UiElement中實(shí)現(xiàn)的)
- 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',
- ];
對于事件系統(tǒng)的回調(diào),在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中有定義,則認(rèn)為該組件聲明了一個事件系統(tǒng)的回調(diào),那么UiElement將綁定這個回調(diào)的具體處理。處理過程如此實(shí)現(xiàn):
- handleCallback(callback, e) {
- if (this.props.callback) {
- this.props.callback(e);
- }
- }
回顧
在UiElement中,實(shí)現(xiàn)了三類公共功能供基本組件類調(diào)用:
- 編輯和組裝CSS類
- 渲染組件本身
- 封裝事件系統(tǒng)的方法回調(diào)
實(shí)現(xiàn)以后,基本組件類的相同處理均被抽離出來,僅剩下一些聲明性質(zhì)的代碼。例如Header組件的實(shí)現(xiàn)被簡化為:
- 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'
- ]);
- /**
- * 標(biāo)題組件
- */
- class Header extends UiElement {
- // 類型定義
- static propTypes = {
- ...PropsHelper.createPropTypes(PROP_TYPES)
- };
- // 默認(rèn)值定義
- static defaultProps = {
- ...PropsHelper.getDefaultPropsValue(PROP_TYPES)
- };
- /**
- * 取得渲染內(nèi)容
- */
- render() {
- // 渲染元素
- let style = this.createElementStyle(this.props, PROP_TYPES) + ' header';
- return super.render(style);
- }
- }
- export default Header;
這樣的好處是顯而易見的:
- 簡化實(shí)現(xiàn)代碼提高可閱讀性
- 封裝共通處理提高可維護(hù)性
- 通過方法覆寫保持可擴(kuò)展性
通過這幾篇,基礎(chǔ)組件的封裝處理應(yīng)該說完了,接下來的幾篇打算說說復(fù)雜組件的實(shí)現(xiàn)。在完成所有組件的封裝后,還打算擴(kuò)展一些復(fù)雜組件的功能(代碼丑,只能多實(shí)現(xiàn)些功能了??傊凸俜阶龀刹灰粯拥?(ㄒoㄒ)/~~)。