[ 一塊兒學React系列 -- 4 ] 透傳的Context

拋轉引玉

經過上一篇的科普咱們知道若是父節點須要向子節點傳遞數據,那麼就得經過Props來實現;那麼擺在咱們眼前的就有一個問題了:現有N個節點而且它們都是嵌套成父子結構,大體以下react

<A>
    <B>
        <C>
            <D>
                ......
                <Last></Last>
            </D>
        </C>
    </B>
</A>

如過last組件須要A組件的某個數據,按照以前的說法咱們可使用Props;可是我以爲通常人都不會這麼作,爲何?一個數據在N個組件中經過Props傳遞,首先寫法上會很榮譽、其次就是極可能在某個節點寫錯了形成最終拿到的數據不是想要的數據,這些都是咱們須要考慮的問題。固然有人會想到使用Redux或者Mobx這種第三方庫來解決,沒毛病;但若是隻是一個小小的需求就引入了一個庫,是否是殺雞用了牛刀?在這個問題上React自己有本身的解決方案:Contextgit

Context是什麼?

目前React的Context API已經出了兩版,在React16.3.0版本以前和以後。實際上咱們開發React項目時候不多會用到這個API(至少小編身邊是這種狀況);並且對於初版的Context就連官方也不建議用,首先是很差用其次是問題多,不過即便如此不堪的技術倒是Redux的基礎技術,真的是厲害了!
後來在React16.3.0版本更新以後,全新的Context API與咱們見面,能夠說是脫胎換骨。官方對Context的介紹是:npm

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

意思就是Context提供了一種經過組件樹傳遞數據的方法,而無需在每一個級別手動傳遞props。能夠看出這個技術恰好能夠用來解決咱們前面提出的問題。redux

Context能夠作什麼?

事實上官方設計這個API的目的是共享可被視爲React組件樹的「全局」數據,例如當前通過身份驗證的用戶,主題或首選語言。意圖言簡意賅,能夠理解成爲React組件樹(從Root節點開始經過不斷組合各個組件造成最終的樹形結構)中注入了一個上下文對象同時將一些全局通用的數據放在這個對象中,這樣咱們就能夠在這個組件樹的任何地方使用這些數據。ide

如何使用Context?

針對新版Context,官方給咱們提供了三個API:學習

  1. React.createContext
  2. Provider
  3. Consumer

經過字面意思你們應該就能猜到它們分別的做用了吧!測試

React.createContext: 用來建立Context對象
Provider: 用來向組件樹發出Context對象
Consumer: 使用Context對象

不過呢,後二者實際上是React.createContext建立出來的對象的組成,用一段代碼來解釋吧:ui

const {Provider, Consumer} = React.createContext(defaultValue);

嗯...就醬紫!!!!
其實寫到這裏我相信用過Redux的朋友就已經開始以爲眼熟了,就是ProvidercreateContext。由於react-redux提供Provider, Redux提供createStore。這也是Redux基於Context API重要物證哈哈....this

實例使用Context

學習技術最終是要有產出的。筆者也一步一步來實現一個簡單例子,功能:經過點擊按鈕對屏幕中數字進行加1操做
首先咱們須要建立兩個js文件:spa

buildContext.js
import {createContext} from 'react';

const defaultData = {};
export const {Provider, Consumer} = createContext(defaultData);

這裏可能有人會有疑問:爲何將建立Context單獨抽離出來?
1) 將Context和組件隔離;由於它們不存在必要的聯繫,Context只是單純的注入組件而已。
2) 由於Provider, Consumer須要配對使用(注意:Provider, Consumer配對使用的前提是它們都來自同一個createContext);咱們能夠在Provider下的任意節點使用Consumer,因此就可能存在Provider, Consumer不在同一個組件的狀況,因此將將建立Context單獨抽離出來使得處理Context更加優雅。

ContextDemo.js
import React, {Component} from 'react'
import {Provider, Consumer} from './buildContext';

class ContextDemo extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        };
    }

    addOne = () => {
        this.setState((preState) => ({
                    count: preState.count + 1
                }
            )
        )
    };

    render() {
        return (
            <div>
                <Provider value={this.state}>
                    <div>
                        <Consumer>
                            {
                                (context) => <p>{context.count}</p>
                            }
                        </Consumer>
                    </div>
                </Provider>
                <input type="button" value="加1" onClick={this.addOne}/>
            </div>
        )
    }
}

export default ContextDemo

這裏咱們重點解釋下Provider與Consumer:

Provider
被視做一個React組件,它的做用就是接收一個value屬性並把它當作最終Context實體注入到Provider的全部子組件中;同時Provider容許被Consumer訂閱一個或多個Context的變更,也就是說Provider內部能夠有N個Consumer而且它們均可以拿到 最新&&相同的Context對象。

如例子所示,咱們將組件的State對象注入到Provider字組件中,若是State發生變化那麼Provider中的Context對象一定會同步發生變化。

Consumer
依然被視做一個React組件,不過不一樣的是它的子組件必須是一個方法而且該方法接收當前Context對象並最終返回一個React節點。同時這裏有兩個問題須要重點關注:

  1. Consumer接收到的Context對象爲離它最近的那個Provider注入的Context對象(且必須是經過value屬性)。由於Provider做爲一個組件也能夠進行嵌套。不過筆者認爲單獨一個React項目最好只存在一個Context對象並且應該做爲一個App級的Context對象(也就是將項目的根節點做爲Provider的子組件)。這樣作筆者認爲有兩個好處:1)全局只有一個Context更有利於方便使用和管理;2)做爲一個App級的Context對象可讓咱們在項目的任何一個地方使用到Context對象,發揮Context最大的力量。
  2. 若是Provider不存在(若是存在那麼必需要有value屬性,不然報錯),那麼Consumer獲取到的Context對象爲最初createContext方法的默認參數。

綜上所述:Provider的value == Consumer子組件(function)的入參

當咱們理解了這兩個概念,咱們再回過頭來看代碼;
咱們將組件的State(this.state)經過Provider注入到其子組件中,其實能夠預料到當咱們更改State時候Context對象也會同步變化最終保持一致。因此:

<Consumer>
    {
        (context) => <p>{context.count}</p>
    }
</Consumer>

此時Consumer的子組件(function)的入參context就能夠認爲是this.state的複製體,因此能夠在方法中獲取到相應的數據而且在點擊按鈕更改了State後Context也發生變化,從而實現UI的從新渲染。

小小的測試

前面有句話說:Provider, Consumer配對使用的前提是它們都來自同一個createContext。所以筆者針對這點作了兩個實驗,目的是測試當Provider, Consumer不是來自同一個createContext會出現什麼狀況。這裏新建兩個文件buildContext.js和ContextTest.js

狀況一

buildContext.js
import {createContext} from 'react';

export const {Provider} = createContext({'name': 'Mario'});
export const {Consumer} = createContext({'age': '26'});
ContextTest.js
import React, {Component} from 'react';
import {Provider, Consumer} from "./buildContext";

class Context extends Component {
    render() {
        return (
            <Provider>{/*name*/}
                <Consumer>{/*age*/}
                    {
                        (context) => (
                            <div>
                                <p>age: {context.age}</p>
                                <p>name: {context.name}</p>
                            </div>
                        )
                    }
                </Consumer>
            </Provider>
        )
    }
}

export default Context;

clipboard.png

運行的結果有點意想不到,Consumer拿到的Context並非離它最近的Provider提供的,而是創造它的createContext方法的默認值,即:export const {Consumer} = createContext({'age': '26'});。再換個寫法看看!

狀況二

buildContext.js
import {createContext} from 'react';

export const NameContext = createContext({'name': 'Mario'});
export const AgeContext = createContext({'age': '26'});
ContextTest.js
import React, {Component} from 'react';
import {NameContext, AgeContext} from "./buildContext";

class Context extends Component {
    render() {
        return (
            <NameContext.Provider value={{'name':'React'}}>{/*name*/}
                <AgeContext.Consumer>{/*age*/}
                    {
                        (context) => (
                            <div>
                                <p>age: {context.age}</p>
                                <p>name: {context.name}</p>
                            </div>
                        )
                    }
                </AgeContext.Consumer>
            </NameContext.Provider>
        )
    }
}

export default Context;;

這裏咱們給Provider提供一個value屬性;

clipboard.png

運行結果與第一種狀況相同;

結論

所以咱們能夠猜想:若是Provider, Consumer不是來自同一個createContext,那麼Consumer獲取到的Context則是本身的createContext方法的默認值,此時的Provider被視爲不存在。

Context的簡單實用就介紹到這裏,本篇不少地方都來自筆者的我的見解,若有理解不當煩請丟磚。
同時筆者也嘗試寫了一個更具備表明性(純屬筆者意淫)的Context應用實例,將Context、ContextHandler、Component分離出來,實現更改相鄰組件的樣式。麻雀雖小五臟俱全,請各位朋友多多海涵!!
對了項目啓動腳本是npm install || npm start

相關文章
相關標籤/搜索