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

NO IMAGE

一、倉庫地址

本文章基於[email protected],講解我是如何使用新版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中使用。

相關文章

如何使用imageset適配移動端高清屏圖片

如何在webpack中做預渲染降低首屏空白時間

HybirdApp模式下,js如何與客戶端交互

RxJS實踐,Vue如何集成RxJS