我們都知道,基於props做組件的跨層級數據傳遞是非常困難並且麻煩的,中間層組件要為了傳遞數據添加一些無用的props。
而react自身早已提供了context API來解決這種問題,但是16.3.0之前官方都建議不要使用,認為會遲早會被廢棄掉。說歸說,很多庫已經采用了context API。可見呼聲由多么強烈。終於在16.3.0之后的版本,react正式提供了穩定的context API,本文中的示例基於v16.3.0之后的context API。
概念
首先要理解上下文(context)的作用以及提供者和消費者分別是什么,同時要思考這種模式解決的是什么問題(跨層級組件通信)。context做的事情就是創建一個上下文對象,並且對外暴露提供者(通常在組件樹中上層的位置)和消費者,在上下文之內的所有子組件,都可以訪問這個上下文環境之內的數據,並且不用通過props。可以理解為有一個集中管理state的對象,並限定了這個對象可訪問的范圍,在范圍之內的子組件都能獲取到它內部的值。提供者為消費者提供context之內的數據,消費者獲取提供者為它提供的數據,自然就解決了上邊的問題。
用法
這里要用到一個小例子,功能就是主題顏色的切換。效果如圖:
根據上邊的概念和功能,分解一下要實現的步驟:
- 創建一個上下文,來提供給我們提供者和消費者
- 提供者提供數據
- 消費者獲取數據
這里的文件組織是這樣的:
├─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()創建出來的。
page組件
沒啥好說的,就是一個容器組件而已
const Page = () => <> <Title/> <Paragraph/> </>
提供者提供數據
提供者一般位於比較上邊的層級,ThemeProvider 接受的value就是它要提供的上下文對象。
// 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的數據的。
廣州品牌設計公司https://www.houdianzi.com PPT模板下載大全https://redbox.wode007.com
v16.3.0之后的版本
v16.3.0之后的版本使用起來比以前簡單了很多。解決嵌套問題的方式也更優雅。由於Provider和Consumer是成對地被創建出來的。即使這一對的Provider於另一對的Consumer的數據結構和值的類型相同,這個Consumer也讓能訪問那個Provider的上下文。這便是解決方法。
總結
對於這個context這個東西。我感覺還是不要在應用里大量使用。就像React-Redux的Provider,或者antd的LocalProvider,差不多用一次就夠,因為用多會使應用里很混亂,組件之間的依賴關系變得復雜。但是React為我們提供的這個api還是可以看到它自身還是想彌補其狀態管理的短板的,況且Hooks中的useReducer出現后,更說明了這一點。