Semantic-UI的React實現(四):基本元素組件的共通處理(父類)實現

上一篇(Semantic-UI的React實現(三):基本元素組件)已經提到過,基本元素組件的實現由於沒有複雜的交互,僅僅是CSS類的編輯和組裝,所以實現原理相對比較簡單。html

但簡單的東西要想作的簡潔,每每不簡單。react

抽象與封裝

想要簡潔高效地封裝數十個基本組件,將組件的相同處理部分抽象出來是很是必要的。在ES6中js新增了class關鍵字(固然這只是一個語法糖,其背後的處理原理仍然是prototype那一套東西。),有了這個關鍵字js在抽象與封裝的思想上比以前更進了一步。segmentfault

當用「繼承」的思想去考慮問題後,組件的共通處理很明顯能夠經過繼承一個共同父類來完成(一般我更願意用接口而非繼承,無奈js學藝不精,不清楚接口繼承如何實現)。繼承之後,全部基本組件的如下處理,都可以由父類的處理完成:this

  1. 編輯和組裝CSS類spa

  2. 渲染組件自己prototype

  3. 封裝事件系統的方法回調code

圖片描述

實現細節

編輯和組裝CSS類

在系列文章二的時候有提到過,基本組件的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中,實現了三類公共功能供基本組件類調用:

  1. 編輯和組裝CSS類

  2. 渲染組件自己

  3. 封裝事件系統的方法回調

實現之後,基本組件類的相同處理均被抽離出來,僅剩下一些聲明性質的代碼。例如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;

這樣的好處是顯而易見的:

  1. 簡化實現代碼提升可閱讀性

  2. 封裝共通處理提升可維護性

  3. 經過方法覆寫保持可擴展性

經過這幾篇,基礎組件的封裝處理應該說完了,接下來的幾篇打算說說複雜組件的實現。在完成全部組件的封裝後,還打算擴展一些複雜組件的功能(代碼醜,只能多實現些功能了。總之要和官方作成不同的/(ㄒoㄒ)/~~)。

相關文章
相關標籤/搜索