對 React Context 的理解以及應用


在React的官方文檔中,Context被歸類為高級部分(Advanced),屬於React的高級API,但官方並不建議在穩定版的App中使用Context。

很多優秀的React組件都通過Context來完成自己的功能:

  • 比如react-redux的 ,就是通過Context提供一個全局態的store;
  • 拖拽組件react-dnd,通過Context在組件中分發DOM的Drag和Drop事件;
  • 路由組件react-router通過Context管理路由狀態等等。

在React組件開發中,如果用好Context,可以讓你的組件變得強大,而且靈活。

簡單說就是,當你不想在組件樹中通過逐層傳遞props或者state的方式來傳遞數據時,可以使用Context來實現跨層級的組件數據傳遞。

使用props或者state傳遞數據,數據自頂下流。

適用於所有16.x版本(舊版本之后會被廢棄)

如何使用Context

如果要Context發揮作用,需要用到兩種組件,一個是Context生產者(Provider),通常是一個父節點,另外是一個Context的消費者(Consumer),通常是一個或者多個子節點。所以Context的使用基於生產者消費者模式。

對於父組件,也就是Context生產者,需要通過一個靜態屬性childContextTypes聲明提供給子組件的Context對象的屬性,並實現一個實例getChildContext方法,返回一個代表Context的純對象 (plain object) 。

import React from 'react'
import PropTypes from 'prop-types'


class ParentComponent extends React.Component {

    state = {
        parentVal:'parentVal'
    }

    // 聲明Context對象屬性
    static childContextTypes = {
        name: PropTypes.string,
        fun: PropTypes.func
    }
  
    // 返回Context對象,方法名是約定好的
    getChildContext () {
        return {
            name: 'parent-msg',
            fun: () => {
                const {parentVal}=this.state;
                console.log( 'from top msg', 'parent-state-parentVal:',parentVal )
            }
        }
    }
  
  render () {
    return (
        <div>
            <p>我是父級或者祖父</p>
        </div>
    )
    
  }
}

而對於Context的消費者,通過如下方式訪問父組件提供的Context。

import React from 'react'
import PropTypes from 'prop-types'

class ChildComponent extends React.Component {
  // 聲明需要使用的Context屬性
  static contextTypes = {
    name: PropTypes.string,
    fun: PropTypes.func
  }
  
  render () {
    const {name,fun} = this.context
    console.log(name)
    fun()
  }
}

子組件需要通過一個靜態屬性contextTypes聲明后,才能訪問父組件Context對象的屬性,否則,即使屬性名沒寫錯,拿到的對象也是undefined。

新版

請謹慎使用,因為這會使得組件的復用性變差。

使用context的通用的場景包括管理當前的locale,theme,或者一些緩存數據,這比替代方案要簡單的多。

API

React.createContext

const MyContext = React.createContext(defaultValue);

創建一個Context對象。當React渲染一個訂閱了這個Context對象的組件,這個組件會從組件樹中離自身最近的那個匹配的Provider中讀取到當前的上下文值。

Context.Provider

<MyContext.Provider value={/* 某個值 */}>

每個Context對象都會返回一個Provider React組件,它允許消費組件訂閱context的變化。

當提供者的value值發生變化時,它內部的所有消費組件都會重新渲染。

提供者及其內部消費者組件都不受制shouldComponentUpdate函數,因此當消費者組件在其祖先組件退出更新的情況下也能更新。

Class.contextType

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;

掛載在類上的contextType屬性會被重賦值為一個由React.createContext()創建的Context對象。

這能讓你使用this.context來消費最近Context上的那個值。你可以在任何生命周期中訪問到它,包括渲染函數中。

可以使用static這個類屬性來初始化你的contextType。

class MyClass extends React.Component {
  static contextType = MyContext;
  render() {
    let value = this.context;
    /* 基於這個值進行渲染工作 */
  }
}

Context.Consumer

<MyContext.Consumer>
  {value => /* 基於 context 值進行渲染*/}
</MyContext.Consumer>

傳遞給函數的value值等同於往上組件樹離這個context最近的提供者提供的value值。如果沒有對應的Provider,value參數等同於傳遞給createContext()的defaultValue。

注意:當 provider 的父組件進行重渲染時,可能會在 consumers 組件中觸發意外的渲染。

示例

關鍵語句 ( 類似與 eventBus 里的 bus.js 作為一個媒介,該句用於方便理解 )

createContext.js

import React from 'react';
export default React.createContext('createContext-test-defaultVal');

提供初始 context 值的組件

parent.js

import React from 'react';
import createContext from './createContext';        // 引入媒介


render() {
    // 在 createContext 內部的 ComTest 組件使用 state 中的 name 值,
    // 而外部的組件使用默認的值
    return (
      <div>
        <createContext.Provider value={this.state.name}>
            // 內部可以獲取到分發的值
            <ComTest fun={this.fun} />
            <Tier0 />
        </createContext.Provider>
        <div>
          <ComTest />
        </div>
      </div>
    );
}

要獲取context值的組件

只要在 createContext.Provider 里,不用管中間隔了多少層,只需要在使用context的組件內引入媒介即可

import React from 'react';
import createContext from './createContext';    // 引入媒介

class ComTest extends React.Component {
  static contextType = createContext;
  componentDidMount(){
    console.log(this.context)
  }
  render(){
    return (
      <div>
        <p>此處為獲取數據的子組件 : {this.context}</p>
      </div>
    )
  }
}
// ComTest.contextType = createContext;

export default ComTest;

多個 Context

為了確保 context 快速進行重渲染,React 需要使每一個 consumers 組件的 context 在組件樹中成為一個單獨的節點。


// Theme context,默認的 theme 是 “light” 值
const ThemeContext = React.createContext('light');
// 用戶登錄 context
const UserContext = React.createContext({name: 'Guest',});




class App extends React.Component {
  render() {
    const {signedInUser, theme} = this.props;

    // 提供初始 context 值的 App 組件
    return (
      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Layout />
        </UserContext.Provider>
      </ThemeContext.Provider>
    );
  }
}




function Layout() {
  return (
    <div>
      <Sidebar />
      <Content />
    </div>
  );
}



// 一個組件可能會消費多個 context
function Content() {
  return (
    <ThemeContext.Consumer>
      {theme => (               // 對應 ThemeContext.Provider 的 value
        <UserContext.Consumer>
          {user => (            // 對應 UserContext.Provider 的 value
            <Child user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>
  );
}

官方文檔:https://zh-hans.reactjs.org/docs/context.html#___gatsby


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM