1、作用
Context 提供了一個(gè)無需為每層組件手動(dòng)添加 props,就能在組件樹間進(jìn)行數(shù)據(jù)傳遞的方法,實(shí)現(xiàn)跨級(jí)父子組件間的通信。
在一個(gè)典型的 React 應(yīng)用中,數(shù)據(jù)是通過 props 屬性自上而下(由父及子)進(jìn)行傳遞的,但這種做法對(duì)于某些類型的屬性而言是極其繁瑣的(例如:地區(qū)偏好,UI 主題),這些屬性是應(yīng)用程序中許多組件都需要的。Context 提供了一種在組件之間共享此類值的方式,而不必顯式地通過組件樹的逐層傳遞 props。
2、使用 Context
(1)React.createContext(defaultValue)
const MyContext = React.createContext(defaultValue);
創(chuàng)建一個(gè) Context 對(duì)象,defaultValue為設(shè)置的默認(rèn)值。
當(dāng) React 渲染一個(gè)訂閱了這個(gè) Context 對(duì)象的組件,這個(gè)組件會(huì)從組件樹中離自身最近的那個(gè)匹配的 Provider 中讀取到當(dāng)前的 context 值。
(2)Context.Provider。
<MyContext.Provider value={/* 某個(gè)值 */}>
每個(gè) Context 對(duì)象都會(huì)返回一個(gè) Provider React 組件,它允許消費(fèi)組件訂閱 context 的變化。Provider 接收一個(gè) value 屬性,傳遞給消費(fèi)組件。一個(gè) Provider 可以和多個(gè)消費(fèi)組件有對(duì)應(yīng)關(guān)系。多個(gè) Provider 也可以嵌套使用,里層的會(huì)覆蓋外層的數(shù)據(jù)。
當(dāng) Provider 的 value 值發(fā)生變化時(shí),它內(nèi)部的所有消費(fèi)組件都會(huì)重新渲染。Provider 及其內(nèi)部消費(fèi)組件都不受制于 shouldComponentUpdate 函數(shù),因此當(dāng)消費(fèi)組件在其祖先組件退出更新的情況下也能更新。
(3) Class.contextType
class MyClass extends React.Component {
componentDidMount() {
let value = this.context;
/* 在組件掛載完成后,使用 MyContext 組件的值來執(zhí)行一些有副作用的操作 */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* 基于 MyContext 組件的值進(jìn)行渲染 */
}
}
MyClass.contextType = MyContext;
掛載在 class 上的 contextType 屬性會(huì)被重賦值為一個(gè)由 React.createContext()創(chuàng)建的 Context 對(duì)象。這能讓你使用 this.context 來獲取最近 Context 上的那個(gè)值。你可以在任何生命周期中訪問到它,包括 render 函數(shù)中。
上面的概念看不懂,沒關(guān)系,下面我們通過擼代碼,一目了然~~~
<script type="text/babel">
/*1、 創(chuàng)建一個(gè) context({id:00000,name:XX,address:'China'}為默認(rèn)值)。*/
const DataContext = React.createContext({id:'00000',name:'XX',address:'China'});
class App extends React.Component {
constructor(props) {
super(props);
this.id = 1000;
this.state = {
obj: {
id: this.id,
name: "CaiKe",
address: "上海徐匯區(qū)"
}
};
}
render() {
setTimeout(() => {
this.setState({
obj: {
id: ++this.id,
name: "Kevin",
address: "上海浦東新區(qū)"
}
});
}, 1500);
/* 使用一個(gè) Provider 來將當(dāng)前的 DataContext 傳遞給以下的組件樹。
* 無論多深,任何組件只要獲取Context,都能讀取這個(gè)值。
*在這個(gè)例子中,我們將this.state.obj作為當(dāng)前的值傳遞下去。
*/
return (
<DataContext.Provider value={JSON.stringify(this.state.obj)}>
<MiddleCom />
</DataContext.Provider>
);
}
}
// 中間的組件再也不必指明往下傳遞 DataContext 了。
function MiddleCom(props) {
return (
<div>
<ChildCom />
</div>
);
}
class ChildCom extends React.Component {
// 3、指定 contextType 讀取當(dāng)前的 DataContext。
// React 會(huì)往上找到最近的 Provider,然后使用它的值。
// 在這個(gè)例子中,當(dāng)前的 DataContext 值為 App 組件中的state的obj的值。
static contextType = DataContext;
constructor(props) {
super(props);
this.value = null;
}
componentDidMount() {
// 4、使用 this.context 來獲取最近 Context 上的那個(gè)值,及在Provider 上設(shè)置的value
this.value = this.context;
}
componentDidUpdate(prevProps, prevState) {
this.value = this.context;
}
render() {
return <div>{this.value}</div>;
}
}
// 也可以這樣設(shè)置組件的 contextType 屬性,賦值為一個(gè)由 React.createContext()創(chuàng)建的 Context 對(duì)象
// ChildCom .contextType = DataContext;
ReactDOM.render(<App />, document.getElementById("root"));
</script>

上面代碼中,創(chuàng)建了一個(gè)DataContext(第一步),設(shè)置默認(rèn)值為{id:'00000',name:'XX',address:'China'};使用DataContext.Provider包裹子組件(第二步),設(shè)置value為App組件的state.obj;最后指定 contextType 讀取當(dāng)前的DataContext(第三步),通過this.context獲取到了在父組件的父組件中設(shè)置的數(shù)據(jù)。
基本大功告成。o(∩_∩)o 哈哈~~
我們?cè)贏pp組件中定時(shí)修改數(shù)據(jù),作為中間組件的MiddleCom,沒有做任何傳遞,ChildCom組件正確的顯示出了數(shù)據(jù)。
(4)Context.Consumer
<MyContext.Consumer>
{value => /* 基于 context 值進(jìn)行渲染*/}
</MyContext.Consumer>
組件訂閱到 context 變更,在函數(shù)式組件中完成訂閱 context。
需要使用函數(shù)做為子元素。這個(gè)函數(shù)接收當(dāng)前的 context 值,返回一個(gè) React 節(jié)點(diǎn)。傳遞給函數(shù)的 value 值等同于往上組件樹離這個(gè) context 最近的 Provider 提供的 value 值。如果沒有對(duì)應(yīng)的 Provider,value 參數(shù)等同于傳遞給 createContext() 的 defaultValue。
// 確保傳遞給 createContext 的默認(rèn)值數(shù)據(jù)結(jié)構(gòu)是調(diào)用的組件(consumers)所能匹配的!
const DataContext = React.createContext({
id: "1234",
name: "張三"
});
function ShowText() {
//訂閱context變更
return (
<DataContext.Consumer>
{({ id, name }) => (
<div>
<div>{id}</div>
<div>{name}</div>
</div>
)}
</DataContext.Consumer>
);
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
id: 1001,
name: "Zhang"
};
}
componentDidMount() {
setInterval(() => {
this.setState((state, props) => ({
id: ++state.id,
name: props.name + "_" + state.id
}));
}, 1000);
}
render() {
// 整個(gè) state 都被傳遞進(jìn) provider
return (
<DataContext.Provider value={this.state}>
<ContentOne />
</DataContext.Provider>
);
}
}
//中間組件1
function ContentOne() {
return (
<div>
<ContentTwo />
</div>
);
}
//中間組件2
function ContentTwo() {
return (
<div>
<ShowText />
</div>
);
}
ReactDOM.render(<App name="張三" />, document.getElementById("root"));
3、注意點(diǎn)
Context 主要應(yīng)用場(chǎng)景在于很多不同層級(jí)的組件需要訪問同樣一些的數(shù)據(jù)。請(qǐng)謹(jǐn)慎使用,因?yàn)檫@會(huì)使得組件的復(fù)用性變差。
如果你只是想避免層層傳遞一些屬性,組件組合(component composition)有時(shí)候是一個(gè)比 context 更好的解決方案。
另外一種無需 context 的解決方案是將深層次的組件自身傳遞下去,通過props來獲取,因而中間組件無需知道這些自己不需要的props。
function Page(props) {
const user = props.user;
const content = <Feed user={user} />;
const topBar = (
<NavigationBar>
<Link href={user.permalink}>
<Avatar user={user} size={props.avatarSize} />
</Link>
</NavigationBar>
);
return (
<PageLayout
topBar={topBar}
content={content}
/>
);
}
這種對(duì)組件的控制反轉(zhuǎn)減少了在你的應(yīng)用中要傳遞的 props 數(shù)量,這在很多場(chǎng)景下會(huì)使得你的代碼更加干凈,使你對(duì)根組件有更多的把控。但是,這并不適用于每一個(gè)場(chǎng)景:這種將邏輯提升到組件樹的更高層次來處理,會(huì)使得這些高層組件變得更復(fù)雜,并且會(huì)強(qiáng)行將低層組件適應(yīng)這樣的形式,這可能不會(huì)是你想要的。
這種模式足夠覆蓋很多場(chǎng)景了,在這些場(chǎng)景下你需要將子組件和直接關(guān)聯(lián)的父組件解耦。如果子組件需要在渲染前和父組件進(jìn)行一些交流,你可以進(jìn)一步使用 render props。
4、總結(jié)
有的時(shí)候在組件樹中很多不同層級(jí)的組件需要訪問同樣的一批數(shù)據(jù)。Context 能讓你將這些數(shù)據(jù)向組件樹下所有的組件進(jìn)行“廣播”,所有的組件都能訪問到這些數(shù)據(jù),也能訪問到后續(xù)的數(shù)據(jù)更新。使用 context 的通用的場(chǎng)景包括管理當(dāng)前的 locale,theme,或者一些緩存數(shù)據(jù),這比替代方案要簡(jiǎn)單的多。
還有很多注意點(diǎn)和細(xì)節(jié)問題,可以去官網(wǎng)Context學(xué)習(xí)。