React組件設計模式-Provider-Consumer

咱們都知道,基於props作組件的跨層級數據傳遞是很是困難而且麻煩的,中間層組件要爲了傳遞數據添加一些無用的props。
而React自身早已提供了context API來解決這種問題,可是16.3.0以前官方都建議不要使用,認爲會早晚會被廢棄掉。說歸說,不少庫已經採用了
context API。可見呼聲由多麼強烈。終於在16.3.0以後的版本,React正式提供了穩定的context API,本文中的示例基於v16.3.0以後的context API。react

概念

首先要理解上下文(context)的做用以及提供者和消費者分別是什麼,同時要思考這種模式解決的是什麼問題(跨層級組件通訊)。api

context作的事情就是建立一個上下文對象,而且對外暴露提供者(一般在組件樹中上層的位置)消費者,在上下文以內的全部子組件,
均可以訪問這個上下文環境以內的數據,而且不用經過props。能夠理解爲有一個集中管理state的對象,並限定了這個對象可訪問的範圍,
在範圍以內的子組件都能獲取到它內部的值。antd

提供者爲消費者提供context以內的數據,消費者獲取提供者爲它提供的數據,天然就解決了上邊的問題。數據結構

用法

這裏要用到一個小例子,功能就是主題顏色的切換。效果如圖:
圖片描述ide

根據上邊的概念和功能,分解一下要實現的步驟:函數

  • 建立一個上下文,來提供給咱們提供者和消費者
  • 提供者提供數據
  • 消費者獲取數據

這裏的文件組織是這樣的:this

├─context.js    // 存放context的文件
│─index.js      // 根組件,Provider所在的層級
│─Page.js       // 爲了體現跨層級通訊的添加的一箇中間層級組件,子組件爲Title和Paragraph
│─Title.js      // 消費者所在的層級
│─Paragraph.js  // 消費者所在的層級

建立一個上下文

import React from 'react'

const ThemeContext = React.createContext()

export const ThemeProvider = ThemeContext.Provider
export const ThemeConsumer = ThemeContext.Consumer

這裏,ThemeContext就是一個被建立出來的上下文,它內部包含了兩個屬性,看名字就能夠知道,一個是提供者一個是消費者。
Provider和Consumer是成對出現的,每個Provider都會對應一個Consumer。而每一對都是由React.createContext()建立出來的。spa

page組件

沒啥好說的,就是一個容器組件而已設計

const Page = () => <>
  <Title/>
  <Paragraph/>
</>

提供者提供數據

提供者通常位於比較上邊的層級,ThemeProvider 接受的value就是它要提供的上下文對象。code

// index.js
import { ThemeProvider } from './context'

render() {
  const { theme } = this.state
  return <ThemeProvider value={{ themeColor: theme }}>
    <Page/>
  </ThemeProvider>
}

消費者獲取數據

在這裏,消費者使用了renderProps模式,Consumer會將上下文的數據做爲參數傳入renderProps渲染的函數以內,因此這個函數內才能夠訪問上下文的數據。

// Title.js 和 Paragraph的功能是同樣的,代碼也差很少,因此單放了Title.js
import React from 'react'
import { ThemeConsumer } from './context'
class Title extends React.Component {
  render() {
    return <ThemeConsumer>
      {
        theme => <h1 style={{ color: theme.themeColor }}>
          title
        </h1>
      }
    </ThemeConsumer>
  }
}

關於嵌套上下文

此刻你可能會產生疑問,就是應用以內不可能只會有一個context。那多個context若是發生嵌套了怎麼辦?

v16.3.0以前的版本

其實v16.3.0以前版本的React的context的設計上考慮到了這種場景。只不過實現上麻煩點。來看一下具體用法:
和當前版本的用法不一樣的是,Provider和Consumer不是成對被建立的。

Provider是一個普通的組件,固然,是須要位於Consumer組件的上層。要建立它,咱們須要用到兩個方法:

  • getChildContext: 提供自身範圍上下文的數據
  • childContextTypes:聲明自身範圍的上下文的結構
class ThemeProvider extends React.Component {
  getChildContext() {
    return {
      theme: this.props.value
    };
  }
  render() {
    return (
      <React.Fragment>
        {this.props.children}
      </React.Fragment>
    );
  }
}
ThemeProvider.childContextTypes = {
  theme: PropTypes.object
};

再看消費者,須要用到contextTypes,來聲明接收的上下文的結構。

const Title = (props, context) => {
  const {textColor} = context.theme;
  return (
    <p style={{color: color}}>
      我是標題
    </p>
  );
};

Title.contextTypes = {
  theme: PropTypes.object
};

最後的用法:

<ThemeProvider value={{color: 'green' }} >
   <Title />
</ThemeProvider>

回到嵌套的問題上,你們看出如何解決的了嗎?

Provider作了兩件事,提供context數據,而後。又聲明瞭這個context範圍的數據結構。而Consumer呢,經過contextTypes定義接收到的context數據結構。
也就至關於Consumer指定了要接收哪一種結構的數據,而這種結構的數據又是由某個Provider提早定義好的。經過這種方式,再多的嵌套也不怕,Consumer只要定義
接收誰聲明的context的結構就行了。若是不定義的話,是接收不到context的數據的。

v16.3.0以後的版本

v16.3.0以後的版本使用起來比之前簡單了不少。解決嵌套問題的方式也更優雅。因爲Provider和Consumer是成對地被建立出來的。即便這一對的Provider於另外一對的
Consumer的數據結構和值的類型相同,這個Consumer也讓能訪問那個Provider的上下文。這即是解決方法。

總結

對於這個context這個東西。我感受仍是不要在應用裏大量使用。就像React-Redux的Provider,或者antd的LocalProvider,差很少用一次就夠,由於用多會使應用裏很混亂,組件之間的依賴關係變得複雜。可是React爲咱們提供的這個api仍是能夠看到它自身仍是想彌補其狀態管理的短板的,何況Hooks中的useReducer出現後,更說明了這一點。

相關文章
相關標籤/搜索