基於Decorator的React高階元件的思路分析與實現

fi3ework發表於2018-02-01

『 文章首發於GitHub Blog

深入react 技術棧一書中,提到了基於Decorator的HOC。而不是直接通過父元件來逐層傳遞props,因為當業務邏輯越來越複雜的時候,props的傳遞和維護也將變得困難且冗餘。

書裡對基於Decorator的HOC沒有給出完整的實現,在這裡實現並記錄一下實現的思路。

整個實現的程式碼放到了我的Github上,是用來獲取豆瓣的電影列表的,npm start即可開箱。

整體思路

img

書裡描述的整體思路,先將整個元件,按照view邏輯抽象為互不重疊的最小的原子元件,使元件間組合更自由。在這裡最小的元件就是SearchInput SelectInput List。原子元件一定是純粹的、木偶式的元件,如果他們自身帶有複雜的互動/業務邏輯,那麼在組合起來以後可想需要修改多少個原子元件,也就失去了相對配置式的優勢。

元件實現

原子元件

這是對原書程式碼稍加修改的SearchInput原子元件,因為沒加Icon,所以改了一下(逃),整體思路不變。原子元件沒什麼可說的,木偶元件就是接收props來實現功能,是對view層邏輯的抽象。

需要一提的是displayName,是用來確定元件的『身份』的,會被包裹它的組合元件用到,後面會提到組合元件。

export default class SearchInput extends PureComponent {
  static displayName = 'SearchInput'

  render() {
    const { onSearch, placeholder } = this.props
    return (
      <div>
        <p>SearchSelect</p>
        <div>
          <Input
            type="text"
            placeholder={placeholder}
            onChange={onSearch}
          />
        </div>
      </div>
    )
  }
}
複製程式碼

Decorator元件

先放程式碼

const searchDecorator = WrappedComponent => {
  class SearchDecorator extends Component {
    constructor(props) {
      super(props)
      this.handleSearch = this.handleSearch.bind(this)
      this.state = {
        keyword: ''
      }
    }

    handleSearch(e) {
      this.setState({
        keyword: e.target.value
      })
      this.props.onSearch(e)
    }

    render() {
      const { keyword } = this.state

      return (
        <WrappedComponent
          {...this.props}
          data={this.props.data}
          keyword={keyword}
          onSearch={this.handleSearch}
        />
      )
    }
  }
複製程式碼

Decorator的作用就是將業務/互動邏輯抽象出來進行了處理,view的邏輯還是交由原子元件來實現,可以看到最後的render渲染的還是wrappedComponent,只不過是在經過Decorator之後多了幾個props,這些props的中有鉤子函式,有要傳遞給原子元件的引數。

這樣,檢視邏輯就由view層抽象,互動/業務邏輯由Decorator來抽象。

組合元件

先上程式碼。

export default class Selector extends Component {
  render() {
    return (
      <div>
        {
          this.props.children.map((item) => {
            // SelectInput
            if (item.type.displayName === 'SelectInput') {
              ...
            }
            // SearchInput
            if (item.type.displayName === 'SearchInput') {
              return React.cloneElement(item,
                {
                  key: 'searchInput',
                  onSearch: this.props.onSearch,
                  placeholder: this.props.searchPlaceholder
                }
              )
            }
            // List
            if (item.type.displayName === 'List') {
              ...
          })
        }
      </div>
    )
  }
}
複製程式碼

組合元件的children為根據不同業務需要包裹起來的原子元件,組合元件的邏輯處理功能來自於Decorator,各種Decorator的鉤子函式或者引數作為props傳遞給了Selector,Selector再用它們去完成原子元件之間的互動。組合元件通過之前提到的displayName為不同的原子元件分配props並根據業務需要進行元件間邏輯互動的調整。

一個 Decorator 只做最簡單的邏輯,只是給元件增加一個原子的智慧特性。業務元件通過組織和拼接 Decorator 來實現功能,而不是改變 Decorator 本身的邏輯。

當我們業務邏輯變得複雜的時候,不要去增加Decorator的複雜度,而是去拼接多個Decorator再通過組合元件去處理具體的業務邏輯,這樣能保證Decorator的可複用性。

業務元件

const FinalSelector = compose(asyncSelectDecorator, selectedItemDecorator, searchDecorator)(Selector)

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

class App extends Component {
  render() {
    return (
      <SearchSelect
        searchPlaceholder={'請搜尋電影'}
        onSearch={(e) => { console.log(`自定義onSearch: ${e.target.value}`) }}
        onClick={(text) => { console.log(`自定義onClick: ${text}`) }}
        url="/v2/movie/in_theaters"
      />
    )
  }
}

複製程式碼

通過compose賦予組合元件不同的邏輯處理功能,然後根據業務需要讓compose後的組合元件包含原子元件,最後給從最外層傳遞引數就完成了。

tips

在實際的場景中也不能濫用HOC,基於Decorator的HOC一般是用來處理偏資料邏輯的部分,而DOM相關的東西就直接簡單粗暴的用父元件就好了。

對比 HOC 正規化 **compose(render)(state) **與父元件(Parent Component)的正規化 render(render(state)),如果完全利用 HOC 來實現 React 的 implement,將操作與 view 分離,也未嘗不可,但卻不優雅。HOC 本質上是統一功能抽象,強調邏輯與 UI 分離。但在實際開發中,前端無法逃離 DOM ,而邏輯與 DOM 的相關性主要呈現 3 種關聯形式:

  • 與 DOM 相關,建議使用父元件,類似於原生 HTML 編寫
  • 與 DOM 不相關,如校驗、許可權、請求傳送、資料轉換這類,通過資料變化間接控制 DOM,可以使用 HOC 抽象
  • 交叉的部分,DOM 相關,但可以做到完全內聚,即這些 DOM 不會和外部有關聯,均可

參考資料

相關文章