基於Decorator的組件擴展實踐

在前端業務開發中,組件化已經成爲咱們的共識。沉澱和複用組件,是提升開發效率的利器。但在組件複用的過程當中,咱們每每會遇到這樣的問題,組件類似,卻在結構或交互上有些許差異,須要對組件進行改造方可知足需求。這個問題以前在 React實踐 - Component Generator 就有所說起。javascript

之初,咱們提出了組件配置式。在業務統一的狀況下,僅僅修改組件用於配置的props就能夠知足業務需求。但隨着業務發生變化致使組件形態發生變化時,咱們就必須不斷增長配置去應對變化,便會出現配置氾濫,而在擴展過程當中又必須保證組件向下兼容,只增不減,使組件可維護性的下降。前端

最近的項目開發中,@JasonHuang 提出了組件組合式開發思想,有效地解決了配置式所存在的一些問題。下面我將詳細闡述其思想與具體實現。java

組件再分離

對於組件的 view 層,咱們指望組件是沒有冗餘的,組件與組件間 view 重疊的部分應當被抽離出來,造成顆粒度更細的組件,使組件組合產生更多的可能。編程

這種 view 細化的組合式思想早已在咱們團隊可視化庫 Recharts 中有所體現。Recharts 避免了複雜的圖表配置,而將圖表進行有效拆分,經過聲明式的標籤進行組合,從而使圖表更具擴展性。json

一樣,咱們在組件上也但願秉承這種思想,先來看一下在現有業務比較典型的三個公共組件:app

組件圖

這三個組件不管在 UI 仍是邏輯上均存在必定共性。在配置式中,咱們會將這三個組件經過一個組件的配置變換來實現,但無疑會提升單個組件內部邏輯的複雜性。echarts

再作一次分離!它們可由 SelectInput、SearchInput 與 List 三個顆粒度更細的組件來組合。而對於顆粒度最細的組件,咱們但願它是純粹的,木偶式的組件。函數式編程

例如 SelectInput 組件,組件狀態徹底依賴傳入的 props,包括 selectedItem (顯示用戶所選項)、isActive (當前下拉狀態)、onClickHeader (反饋下拉狀態)以及placeholder (下拉框提示)。咱們來看一下它的簡要實現:函數

class SelectInput extends Component {
  static displayName = 'SelectInput';
  
  render() {
    const { selectedItem, isActive, onClickHeader, placeholder } = this.props;
    const { text } = selectedItem || {};
    return (
      <div onClick={onClickHeader}>
        <Input 
          type="text"
          disabled
          value={text}
          placeholder={placeholder}
        />
        <Icon className={isActive} name="angle-down" />
      </div>
    );
  }
}

當組件被再次分離後,咱們能夠根據業務中的組件形態對其進行任意組合,造成統一層,擺脫在原有組件上擴展的模式,有效提升組件的靈活性。組件化

邏輯再抽象

那麼有了 view 細化再重組的公共組件後,是否是就能夠愉快地開發了?

是的,但組件層面的抽象不該該只停留在 view 層面,組件中的相同交互邏輯和業務邏輯也應該進行抽象。

ReactEurope 2016 小記 - 上 中提到複用高階函數的思想,編寫 Higher-Order Components (高階組件)來爲基礎組件增長新的功能。

Higher-Order Components = Decorators + Components。在咱們的組件中,也正是貫穿着這樣函數式的思想,來完成組件邏輯上的抽象,例如:

// 完成SearchInput與List的交互
const SearchDecorator = Wrapper => {
  class WrapperComponent extends Component {
    handleSearch(keyword) {
      this.setState({
        data: this.props.data,
        keyword,
      });
      this.props.onSearch(keyword);
    }

    render() {
      const { data, keyword } = this.state;
      return (
        <Wrapper
          {...this.props}
          data={data}
          keyword={keyword}
          onSearch={this.handleSearch.bind(this)}
        />
      );
    }
  }
  
  return WrapperComponent;
};

// 完成List數據請求
const AsyncSelectDecorator = Wrapper => {
  class WrapperComponent extends Component {
    componentDidMount() {
      const { url } = this.props;
      
      fetch(url)
      .then(response => response.json())
      .then(data => {
        this.setState({
          data,
        });
      });
    }

    render() {
      const { data } = this.state;
      return (
        <Wrapper
          {...this.props}
          data={data}
        />
      );
    }
  }

  return WrapperComponent;
}

擁有 Decorator 以後,咱們就能賦予組件能力了,例如合成 Search 組件:

@SearchDecorator
class Search extends Component {
  render() {
    return (
      <Selector
        {...this.props}
      >
        <SearchInput />
        <List />
      </Selector>
    );
  }
}

那麼當咱們將邏輯抽象成爲多個 Decorator 時,又該如何去組合呢?你是否還記得 React Mixin 的前世此生 中提到的方法?沒錯,就是compose!這裏建議讀者 review 這篇文章,順便回顧一下Mixin與高階組件的不一樣點。

// SelectedItemDecorator爲List與SelectInput的交互,讀者能夠自行嘗試實現
const FinalSelector = compose(AsyncSelectDecorator, SearchDecorator, SelectedItemDecorator)(Selector);

class SearchSelect extends Component {
  render() {
    return (
      <FinalSelector
        {...this.props}
      >
        <SelectInput />
        <SearchInput />
        <List />
      </FinalSelector>
    );
  }
}

小結

圖片描述

在配置式組件內部,組件與組件間以及組件與業務間是緊密關聯的,而對於開發人員而言須要完成的僅僅是配置的工做。而組合式意圖打破這種關聯,尋求單元化,經過顆粒度更細的基礎組件與抽象組件共有交互與業務邏輯的 Decorator,使組件更靈活,更易擴展,也使開發者可以完成對於基礎組件的自由支配。

雖然組合式確實能解決配置式所存在的一些問題,但多層 Decorator 帶來的多層包裹,會對組件理解和調試形成必定困難,也"不能"使用外部公有的方法。同時組合式所基於的函數式編程的思想可否被整個團隊所接受,也是咱們須要考量的問題。

相關文章
相關標籤/搜索