在平時使用react的過程當中,數據都是自頂而下的傳遞方式,例如,若是在頂層組件的state存儲了theme主題相關的數據做爲整個App的主題管理。那麼在不借助任何第三方的狀態管理框架的狀況下,想要在子組件裏獲取theme數據,就必須的一層層傳遞下去,即便二者之間的組件根本不須要該數據;就如同下圖所示,而且若是App的層級越深,這之間的層層傳遞對開發者來講可謂是災難。 react
正由於有了跨越層級傳遞值的這麼一種需求,其實官方也提供了context的機制。經過context,咱們就可以在子組件裏獲取祖先組件裏的值,而不須要層層傳遞。其實不少的狀態管理框架與react結合的庫就是使用了context特性,例如著名的react-redux。git
在v16.3以前的context只是官方的實驗性API,其實官方是不推薦開發者使用的,可是架不住不少框架依舊在使用它;因此官方在v16.3發佈的新的context API,新的API會更加的易用,本文也是以v16.3爲準。github
在新的context API中,React提供了一個createContext的方法,該方法返回一個包含了Provider,Consumer的對象,而Provoider,Consumer對象就是新API的重點。redux
咱們先看一個簡單的例子,再來說解API。在本案例中,在頂層父組件的state存儲着控制這個App的theme的一些屬性,使用context來跨組件傳遞這些屬性,使得底層組件可以直接獲得這些屬性。api
首先在themeContext.js文件中定義context,並導出Provider以及Consumer:app
import {createContext} from "react";
export const {Provider, Consumer} = createContext({
color: "green",
fontSize: "20px"
});
複製代碼
createContext須要傳遞一個參數,叫作defaultValue。這個值會在何時起做用呢?這個稍後解釋。框架
而後咱們就能夠直接在頂層的App組件中,直接使用Provider:ide
import React, {Component} from 'react';
import {Provider} from "./context/themeContext";
import Parent from "./Parent";
class App extends Component {
state = {
color: "red",
fontSize: "16px"
};
render() {
return (
<div className="App"> <Provider value={this.state}> <Parent/> </Provider> </div>
);
}
}
export default App;
複製代碼
咱們直接在頂層的組件裏使用Provider組件,而且Provider組件有一個value屬性用於傳遞context的實際的value。而後咱們就能夠在底層的Child組件中獲得這些value來使用。函數
層級關係:App -> Parent -> Childthis
import React, {PureComponent} from "react";
import {Consumer} from "./context/themeContext";
class Child extends PureComponent {
render() {
return <Consumer> { style => <div style={style}>This is Child Component that gets style value through context.</div> } </Consumer>
}
}
export default Child;
複製代碼
在Child組件中使用Consumer,就可以獲得上層所傳遞context的值;Consumer的須要一個函數做爲子元素,該函數的參數就是上層所傳遞context value,而後就能夠返回該組件具體的組件樣式了。
這就是一個簡單的使用context的例子,能夠看到context的API是很是簡單的,也可容易使用,再簡單總結一下API:
看到這裏,你可能會有一個疑惑:爲何createContext須要一個defaultValue,而Provider還須要一個實際的value?到底defaultValue是何時起做用呢?先拋出結論:只有在上層組件沒有提供Provider組件時,下層組件的Consumer纔會直接使用defaultValue做爲子函數的參數傳遞。以本例子爲例,只有在App組件壓根沒有使用Provider組件時,Child組件中的Consumer的子函數參數纔會是{ color: "red", fontSize: "16px" }
這個defaultValue,其餘狀況都不會使用到這個值。這個地方有一個常見的誤解:就是不給上層組件的Provider的value屬性,或者讓value={undefined}
時,就會使用defaultValue,這是不對的!!!請切記,你們也能夠本身嘗試,看看是否是這個結論。
雖然使用了Consumer可以讓咱們很方便的獲得context的value,可是若是不少子元素要獲得context的值,都去先調用Consumer,再在它的子函數裏返回真正的組件內容,會顯得十分的累贅。因此咱們能夠對Consumer進行一個簡單的封裝,封裝一個connect的方法。去實現相似於react-redux其中的connect函數的效果。connect方法的代碼以下:
import React from "react";
import {Consumer} from "./context";
export default mapState => {
return WrappedComponent => {
const Component = props => (<Consumer> { value => { let mappedProps = mapState(value); return <WrappedComponent {...props} {...mappedProps}/> } } </Consumer>); Component.displayName = `connect(${WrappedComponent.displayName || WrappedComponent.name || "Component"})`; return Component; } }; 複製代碼
簡單解釋一下:connect方法須要傳入一個mapState方法,mapState方法是context的value映射方法,當調用connect方法後,會依舊返回一個函數;該函數實際是一個高階函數工廠,將傳入的WrappedComponent組件用Consumer包裹裏面,並結合以前的mapState映射獲得具體的計算後的props屬性,並把這些props屬性都賦予給WrappedComponent。這樣,咱們在以後想要獲得context時,只須要簡單調用一下該方法便可。
再結合一個例子看看怎麼使用connect方法:假如如今有一個App用戶顯示學生的相關信息;學生的信息包含了name,age,gender三個屬性;此外有兩個組件Student、StudentGender;Student用於顯示學生的name,age,而且有一個+
按鈕,點擊就會在當前年齡加一歲。
層級關係以下:App -> StudentContainer -> Student
App組件的代碼以下:
import React, {Component} from 'react';
import {Provider} from "./context";
import StudentContainer from "./StudentContainer";
class App extends Component {
onIncreaseAge = () => {
this.setState(preState => ({
age: preState.age + 1
}))
};
state = {
name: "張三",
age: 12,
gender: "男",
onIncreaseAge: this.onIncreaseAge
};
render() {
return (
<div className="App"> <Provider value={this.state}> <StudentContainer/> </Provider> </div>
);
}
}
export default App;
複製代碼
在App組件中,咱們將student的屬性以及增長年齡的方法一同傳遞給了context,使得子組件既能得到屬性,也能調用修改屬性的方法。
Student組件的代碼以下:
import React from "react";
import {connect} from "./context";
const Student = ({studentName, studentAge, onIncreaseAge}) => {
return <div> <span className="title">Student:</span> <ul> <li>name: {studentName}</li> <li>age: {studentAge} <button onClick={onIncreaseAge}>+</button> </li> </ul> </div>;
};
const mapState = state => ({
studentName: state.name,
studentAge: state.age,
onIncreaseAge: state.onIncreaseAge
});
export default connect(mapState)(Student);
複製代碼
能夠看到,當咱們使用了connect方法後,Student組件就變成了一個傻瓜組件,只須要專心負責顯示數據便可。
以上就是關於context的簡單介紹,能夠看到它確實十分簡單的實現了跨層級傳遞數據的功能。因此當咱們想要跨層級傳遞數據時,而數據自己要傳遞的地方很少,這個時候每每不想再引入一個更復雜的狀態管理框架(如redux等),這個時候,context會是一個十分不錯的選擇。 本文所涉及到的案例的地址在此,其中第一個案例在分支sample-theme
中,第二個案例在分支encapsulate
中。
若是對本文有什麼意見和建議,歡迎討論和指正!!!