咱們都知道,基於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
沒啥好說的,就是一個容器組件而已設計
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以前版本的React的context的設計上考慮到了這種場景。只不過實現上麻煩點。來看一下具體用法:
和當前版本的用法不一樣的是,Provider和Consumer不是成對被建立的。
Provider是一個普通的組件,固然,是須要位於Consumer組件的上層。要建立它,咱們須要用到兩個方法:
自身範圍
上下文的數據自身範圍
的上下文的結構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以後的版本使用起來比之前簡單了不少。解決嵌套問題的方式也更優雅。因爲Provider和Consumer是成對地被建立出來的。即便這一對的Provider於另外一對的
Consumer的數據結構和值的類型相同,這個Consumer也讓能訪問那個Provider的上下文。這即是解決方法。
對於這個context這個東西。我感受仍是不要在應用裏大量使用。就像React-Redux的Provider,或者antd的LocalProvider,差很少用一次就夠,由於用多會使應用裏很混亂,組件之間的依賴關係變得複雜。可是React爲咱們提供的這個api仍是能夠看到它自身仍是想彌補其狀態管理的短板的,何況Hooks中的useReducer出現後,更說明了這一點。