React 全新的 Context API

React 全新的 Context API

來自專欄 FE-player4 人贊了文章

本文首發於 loveky 的流水賬

Context API 可以說是 React 中最有趣的一個特性了。一方面很多流行的框架(例如react-reduxmobx-reactreact-router等)都在使用它;另一方面官方文檔中卻不推薦我們使用它。在 Context API 的文檔中有下面這段話:

The vast majority of applications do not need to use context.

If you want your application to be stable, dont use context. It is an experimental API and it is likely to break in future releases of React.

為何會出現這種情況呢?這還得讓我們從現有版本 Context API 要解決的問題已經它自身的缺陷說起。

現有版本 Context API 的使用場景以及缺陷

我們都知道在 React 中父子組件可以通過 props 自頂向下的傳遞數據。但是當組件深度嵌套時,從頂層組件向最內層組件傳遞數據就不那麼方便了。手動在每一層組件上逐級傳遞 prop 不僅書寫起來很繁瑣同時還會為夾在中間的組件引入不必要的 prop。這時 Context API 就派上用場了。你只需要在外層組件上聲明要傳遞給子組件的 Context:

class Parent extends React.Component { getChildContext() { return {color: "purple"}; }}Parent.childContextTypes = { color: PropTypes.string};

然後就可以在任意一級子組件上訪問 Context 里的內容了:

class Child extends React.Component { render() { return ( <p> {this.context.color} </p> ); }}Child.contextTypes = { color: PropTypes.string};

現有 Context API 雖然使用起來不算複雜,但當和 shouldComponentUpdate 搭配使用時就很容易出問題。

讓我們通過一個小例子來簡單說明,假設有以下組件結構:

<A> <B> <C /> </B></A>

其中組件 A 會通過 getChildContext 設置 Context,組件 C 通過 this.context 讀取 Context。

當組件 A 要更新 Context 的時候發生什麼呢?

  1. 組件 A 通過 setState 設置新的 Context 值同時觸發子組件的 rerender。
  2. 組件 B rerender。
  3. 組件 C rerender,並在自己的 render 方法中拿到更新後的Context。

整個流程看起來好像沒什麼問題。如果我們在組件 B 上定義了 shouldComponentUpdate 會發生什麼呢?

  1. 組件 A 通過 setState 設置新的 Context 值同時觸發子組件的 rerender。
  2. 組件 B 執行 shouldComponetUpdate,由於組件 B 自身並不依賴 Context,所以 shouldComponetUpdate 檢測到 state 與 prop 均未變化因此返回 false。無需重新 render。
  3. 由於 B 組件沒有 rerender。這導致組件 C 也不會rerender,因此也就無法獲取到最新的 Context 值。

由於 shouldComponentUpdate 是一個 React 開發人員經常使用的優化方法。所以如果代碼里使用了現有的 Context API 很大概率會遇到上述問題。

那麼有沒有解決方案可以讓現有 Context API 和 shouldComponetUpdate 完美配合呢?答案是有的。這篇文章里有詳細的討論。但該方案屬於比較 hack 的方式,且對新手並不友好。所以 React 的官方文檔里並不建議我們使用現有的 Context API。

新版 Context API

這種情況在去年 12 月 7 號迎來了改變。@acdlite 在 reactjs/rfcs中發起了一個名為『New version of context』 的 PR。帶來了全新的 Context API 提案。2 天后,包含新 Context API 具體實現的 PR 也提交到了 React 代碼庫。這兩個 PR 都在今年 1 月 25 號被合併入各自的代碼庫。

首先讓我們來看看新版 Context API 都由哪幾部分組成:

  • React.createContext 方法用於創建一個 Context 對象。該對象包含 ProviderConsumer 兩個屬性,分別為兩個 React 組件。
  • Provider 組件。用在組件樹中更外層的位置。它接受一個名為 value 的 prop,其值可以是任何 JavaScript 中的數據類型。
  • Consumer 組件。可以在 Provider 組件內部的任何一層使用。它接收一個名為 children 值為一個函數的 prop。這個函數的參數是 Provider 組件接收的那個 value prop 的值,返回值是一個 React 元素(一段 JSX 代碼)。

用代碼描述是這樣的:

type Context<T> = { Provider: Provider<T>, Consumer: Consumer<T>,};interface React { createContext<T>(defaultValue: T): Context<T>;}type Provider<T> = React.Component<{ value: T, children?: React.Node,}>;type Consumer<T> = React.Component<{ children: (value: T) => React.Node,}>;

以下是一段基於新 Context API 編寫的代碼:

import React from "react";import { render } from "react-dom";const NameContext = React.createContext("Jack");;class Hello extends React.PureComponent { render() { return ( <NameContext.Consumer> {name => <h1>Hello *{name}* who come from the new Context API!</h1>} </NameContext.Consumer> ); }}class App extends React.Component { constructor() { super(); this.state = { name: "Michael" }; this.modify = this.modify.bind(this); } modify() { this.setState({ name: this.state.name + "!" }); } render() { return ( <div> <button onClick={this.modify}>Modify Context Value</button> <NameContext.Provider value={this.state.name}> {this.props.children} </NameContext.Provider> </div> ); }}render( <App> <Hello /> </App>, document.getElementById("root"));

點此查看這段代碼的運行效果。

下面讓我們結合這段代碼來說一說新版 Context API 的幾個特點:

  1. ProviderConsumer 必須來自同一次 React.createContext 調用。也就是說 NameContext.ProviderAgeContext.Consumer 是無法搭配使用的。
  2. React.createContext 方法接收一個默認值作為參數。當 Consumer 外層沒有對應的 Provider 時就會使用該默認值。
  3. Provider 組件的 value prop 值發生變更時,其內部組件樹中對應的 Consumer 組件會接收到新值並重新執行 children 函數。此過程不受 shouldComponentUpdete 方法的影響。前面的示例代碼中,Hello 組件繼承自 React.PureComponent 但頁面依然能正確顯示足以說明這一點。
  4. Provider 組件利用 Object.is 檢測 value prop 的值是否有更新。注意 Object.is=== 的行為不完全相同。具體細節請參考 <code>Object.is</code> 的 MDN 文檔頁。
  5. Consumer 組件接收一個函數作為 children prop 並利用該函數的返回值生成組件樹的模式被稱為 Render Props 模式。詳細介紹請參考相關 React 文檔

以上就是關於 React 全新 Context API 的介紹了。按照計劃,全新的 Context API 會隨著 React 16.3.0 版本發布。如果你想現在就體驗一把,可以通過以下命令安裝 alpha 版本:

yarn add react@next react-dom@next

延伸閱讀

  • rfcs/text/0002-new-version-of-context.md
  • Reacts ?? new Context API
  • Replacing redux with the new React context API
  • Bitmasks and the new React Context API
  • How to safely use React context

推薦閱讀:

關於 React 和 WordPress
anujs1.4.0發布
[譯]Render props、render callback 和高階組件皆可互換
Redux其實很簡單(原理篇)
dva系列2 - 數據邏輯哪兒去了?

TAG:React | 前端開發 |