React中Context的API

我的掘金地址:https://juejin.im/post/5c9b10...

1. Context

關於context官網文檔有如下的描述:

  1. If you want your application to be stable, don't use context. It is an experimental API and it is likely to break in future releases of React.
  2. If you aren't familiar with state management libraries like Redux or MobX, don't use context.
  3. If you aren't an experienced React developer, don't use context. There is usually a better way to implement functionality just using props and state.

綜上所述就是不要使用context這個API。
雖然說不要用,但是我們也是要了解下這個API到底是幹嘛的,畢竟有些優秀的庫都是通過這個API實現而來,如:React-Redux。

簡單瞭解context的作用就是在某個父組件中定義一個全局狀態,這個狀態可以在該父組件下的所有子組件中跨級傳遞共享。目前有兩個版本分別是16.x之前和16.x之後的版本。

2. 老版本的Context

在老版本中有如下幾個方法:

getChildContext: 在父組件中聲明一個函數,返回的結果是一個對象,這個對象就是context,可以對子組件進行共享的狀態。

childContextTypes: 在父組件中聲明,執行context中的數據類型,如果不指定會產生錯誤。

contextTypes: 在子孫組件中進行聲明,指定要接受context中哪些數據類型。

Tip: React.PropTypes has moved into a different package since React v15.5. Please use the prop-types library instead to define contextTypes.

如上,react15.5已經棄用React.PropTypes,需要安裝prop-types庫。

看個小例子:

//父組件
import React from 'react'
import DemoSun from './componets/DemoSun'
import propTyps from 'prop-types'

class Demo extends React.Component {

  getChildContext() {
    return {
      color: 'red'
    }
  }
  render() {
    return (
      <div>
        DEMO
        我是什麼顏色的太陽:<DemoSun />
      </div>
    )
  }
}

Demo.childContextTypes = {
  color: propTyps.string
}

export default Demo 


//子組件
import React from 'react'
import propTyps from 'prop-types'

class DemoSun extends React.Component {

  render() {
    return (
      <div>
        DemoSun
        {this.context.color}
      </div>
    )
  }
}

DemoSun.contextTypes = {
  color: propTyps.string
}

export default DemoSun

結果如下,子組件可以獲取context中的color的值。


如果contextTypes定義在某個組件中,則這個組件的生命週期函數中會增加一個參數:

constructor(props, context)
componentWillReceiveProps(nextProps, nextContext)
shouldComponentUpdate(nextProps, nextState, nextContext)
componentWillUpdate(nextProps, nextState, nextContext)
componentDidUpdate(prevProps, prevState, prevContext)

如果在無狀態組件中使用context則如下:

const PropTypes = require('prop-types');

const Button = ({children}, context) =>
  <button style={{background: context.color}}>
    {children}
  </button>;

Button.contextTypes = {color: PropTypes.string};

關於老版的context就介紹到此,來關注下新版本的context。

3. 新版本的Context

新版本中使用ProviderConsumer模式,在頂層Provider中傳入value,在子孫中的Consumer中獲取該值,並且能夠傳遞函數,用來修改context。

  • React.createContext(args):
const Mycontext = React.createContext(defaultValue)

新版的是通過該方法初始化一個context對象。當React渲染了一個訂閱了這個Context對象的組件,這個組件會從組件樹中離自身最近的那個匹配Provider中讀取到當前的context值。只有當組件所處的樹中沒有匹配到Provider時,其defaultValue參數纔會生效。

  • Context.Provider
<Mycontext.Provider value={/*某個值*/}></Mycontext.Provider>

每個Context對象都會返回一個Provider組件。它允許消費組件訂閱context變化。其有一個value屬性,傳遞給消費組件。一個Provider可以和多個消費組件有對應關係,多個Provider也可以嵌套使用,裏層的會覆蓋外層數據。

當Provider的value值發生變化時,它內部的所有消費者組件都會重新渲染。Provider及其內部consumer組件都不受shouldComponentUpdate函數的影響,無論shouldComponentUpdate返回true或者false,因此當consumer組件在其祖先組件退出更新的情況下也可以更新。

  • Class.contexType

掛載在class上的contextType靜態屬性會被賦值爲一個由React.createContext()的Context對象。這能讓你使用this.context來消費最近Context上的那個值。你可以在任何生命週期中訪問它,包括在render中。

class MyClass extends React.Component {
  componentDidMount() {
    let value = this.context;
    /* 在組件掛載完成後,使用 MyContext 組件的值來執行一些有副作用的操作 */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /* 基於 MyContext 組件的值進行渲染 */
  }
}
MyClass.contextType = MyContext;
  • Context.Consumer
<MyContext.Consumer>
    {value=>/*基於context值進行渲染*/}
</MyContext.Consumer>

這裏,React組件也可以訂閱到context變更。這能讓你在函數式組件中完成訂閱context。Consumer的children必須是一個函數。

這需要函數作爲子元素這種做法。這個函數接受當前的context值,返回一個react節點。傳遞給函數的value值等同於往上組件樹離這個context最近的Provider提供的value值。如果沒有對應的Provider,value參數等於傳遞給createContext()的defaultValue。

4. 注意事項

context會使用參考標識來決定何時進行渲染。這樣就會當provider的父組件進行重新渲染時,可能會在consumer組件中觸發意外的渲染。如下:

class App extends React.Componenet{
    render() {
        return (
            <Provider value={{text: 'text'}}>
                <Demo />
            </Provider>
        )
    }
}

如上,每次value都會創建一個新的對象。爲了避免這種情況,我們可以將其提出到state中進行管理。

class App extends React.Componenet{
    constructor(props) {
        super(props)
        this.state = {
            text: 'text'
        }
    }
    render() {
        return (
            <Provider value={{text: this.state.text}}>
                <Demo />
            </Provider>
        )
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章