React使用新版Context構建元件樹工具注入

雲棲直播~發表於2018-06-30

一、倉庫地址

本文章基於React@16.3.0,講解我是如何使用新版Context api做工具注入的。github地址

二、為什麼要向元件注入工具

打個比方,在一個元件樹中,通常可能會有多個元件會使用到ajax請求伺服器獲取資料,這時候你就必須在每個元件中引入ajax相關的庫才能使用,如下:

import ajax from `ajax`

export default class App extends Component {
  ...

  componentDidMount () {
    ajax.get(`http://xxx.com/a`)
      .then(res => {
          ...
      })
  }

  ...
}

而每個元件對於工具的依賴也不相同,那麼則會有相當多的庫引入語句。

// a.jsx
import ajax from `ajax`

//b.jsx
import ajax from `ajax`
import storage from `storage`

然而我並不想每一個元件在我需要用到這種基礎工具的時候都要去單獨引入這些工具,那麼對於多入口的react專案來說,我們可不可以在入口處讓工具存放在元件樹的頂層,需要的時候我就將頂層的工具注入到對應的子元件,然後通過this去訪問對應的工具進行使用呢

// 通過引入一個註解就可以注入頂層放置的工具函式
import { injectMethods } from `provider`

@injectMethods
export default class TestProvider extends Component {
  ...

  componentDidMount () {
    this.props.ajax.get(`http://xxx.com/a`)
      .then(res => {
          ...
      })

    this.props.storage.setItem(`key`, `value`)
  }

  ...
}

這樣我們就不需要關注元件的工具引入了,統一在頂層進行管理,當然你也可以注入其餘的全域性屬性

三、實現

新版的React採取宣告式的方式使用Context api,這是react官方Context用法的說明,下面我們先看看我們的入口是如何將工具儲存在頂層的Provider中的

// 入口js

// provider webpack中定義了alias
import { setProvider } from `provider`
import detectAgent from `provider/detect-agent`
import dateFormat from `provider/format/date-format`
import storage from `provider/storage`
import urlutils from `provider/url-utils`

// 頂層需要注入的方法
let providers = Object.assign(
  {},
  detectAgent,
  dateFormat,
  storage,
  urlutils
)

render(
  // Context.Provider注入方法,供子元件使用
  setProvider(App, providers),
  document.getElementById(`app`)
)
// provider/index.js

export const Context = React.createContext()

// 傳入根節點與基礎工具,採用Context.Provider對跟元件進行包裝
export const setProvider = (RootComponent, providers) => {
  return (
    <Context.Provider value={providers}>
      <RootComponent></RootComponent>
    </Context.Provider>
  )
}

/**
 * 用註解@的方式給子元件注入全域性方法
 * @param {component} RealComponent 
 * 
 * 如: @injectMethods
 *     class TestComponent extends Component {}
 * 
 * 通過上面的方式就可將儲存在頂層的方法注入進元件的props屬性中
 */
export const injectMethods = (RealComponent) => {
  return class extends Component {
    render () {
      return (
        <Context.Consumer>
          { value => <RealComponent {...value} {...this.props}></RealComponent> }
        </Context.Consumer>
      )
    }
  }
}

在入口檔案中,我們會將方法統一放置在Context.Provider元件中,之後通過Context.Consumer元件去獲取Provider中儲存的value,將工具注入到子元件的props中,injectMethods是一個註解,如果你的一個元件需要獲取頂層的工具,直接在元件上進行註解就可以注入到props中了

import { injectMethods } from `provider`

// 給TestProvider的props注入頂層的方法
@injectMethods
export default class TestProvider extends Component {
  componentDidMount () {
    // 任意元件都可通過injectMethods注入全域性方法
    console.log(`test-provider已向props注入全域性方法`)
    console.log(this.props)
  }

  render () {
    let { $dateFormat } = this.props;

    return (
      <div>
        <p>test-provider</p>
        <p>{ $dateFormat(new Date()) }</p>
      </div>
    )
  }
}

當然,這種方式還可以對統一對元件樹中的一些常量進行管理,需要的時候就注入到props中使用。

原文釋出時間為:2018年06月28日
原文作者:bug給我滾

本文來源: 掘金 如需轉載請聯絡原作者


相關文章