1. 程式人生 > >[ 一起學React系列 -- 4 ] 透傳的Context

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

拋轉引玉

通過上一篇的科普我們知道如果父節點需要向子節點傳遞資料,那麼就得通過Props來實現;那麼擺在我們眼前的就有一個問題了:現有N個節點並且它們都是巢狀成父子結構,大致如下

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

如過last元件需要A元件的某個資料,按照之前的說法我們可以使用Props;但是我覺得一般人都不會這麼做,為什麼?一個數據在N個元件中通過Props傳遞,首先寫法上會很榮譽、其次就是很可能在某個節點寫錯了造成最終拿到的資料不是想要的資料,這些都是我們需要考慮的問題。當然有人會想到使用Redux

或者Mobx這種第三方庫來解決,沒毛病;但如果只是一個小小的需求就引入了一個庫,是不是殺雞用了牛刀?在這個問題上React本身有自己的解決方案:Context

Context是什麼?

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

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

意思就是Context提供了一種通過元件樹傳遞資料的方法,而無需在每個級別手動傳遞props。可以看出這個技術剛好可以用來解決我們前面提出的問題。

Context可以做什麼?

事實上官方設計這個API的目的是共享可被視為React元件樹的“全域性”資料,例如當前經過身份驗證的使用者,主題或首選語言。意圖言簡意賅,可以理解成為React元件樹(從Root節點開始通過不斷組合各個元件形成最終的樹形結構)中注入了一個上下文物件同時將一些全域性通用的資料放在這個物件中,這樣我們就可以在這個元件樹的任何地方使用這些資料。

如何使用Context?

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

  1. React.createContext
  2. Provider
  3. Consumer

通過字面意思大家應該就能猜到它們分別的作用了吧!

React.createContext: 用來建立Context物件Provider: 用來向元件樹發出Context物件Consumer: 使用Context物件

不過呢,後兩者其實是React.createContext創建出來的物件的組成,用一段程式碼來解釋吧:

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

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

例項使用Context

學習技術最終是要有產出的。筆者也一步一步來實現一個簡單例子,功能:通過點選按鈕對螢幕中數字進行加1操作首先我們需要建立兩個js檔案:

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物件。因為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的重新渲染。

Context的簡單實用就介紹到這裡。不過筆者也嘗試寫了一個更具有代表性(純屬筆者意淫)的Context應用例項。將Context、ContextHandler、Component分離出來,實現更改相鄰元件的樣式。麻雀雖小五臟俱全,請各位朋友多多海涵!!對了專案啟動指令碼是npm install || npm start