[ 一起學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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章