Context 提供了一個無需為每層組件手動添加 props,就能在組件樹間進(jìn)行數(shù)據(jù)傳遞的方法。
在一個典型的 React 應(yīng)用中,數(shù)據(jù)是通過 props 屬性自上而下(由父及子)進(jìn)行傳遞的,但這種做法對于某些類型的屬性而言是極其繁瑣的(例如:地區(qū)偏好,UI 主題),這些屬性是應(yīng)用程序中許多組件都需要的。Context 提供了一種在組件之間共享此類值的方式,而不必顯式地通過組件樹的逐層傳遞 props。
Context的使用場景
Context 是為了在一個組件樹內(nèi)部共享"全局"的數(shù)據(jù)而設(shè)計的, 因此當(dāng)涉及一些全局可能需要獲取的數(shù)據(jù)可以考慮使用Context, 例如: 當(dāng)前用戶的認(rèn)證、頁面屬性、首先語言等等.
使用之前的考慮
- 謹(jǐn)慎使用Context, 因為這會使得組件之間的復(fù)用性變差.
- 如何避免組件間props的層層傳遞問題?
- 方案1: React Context
- 方案2: 組件組合(component composition)
API
React.createContext
- 創(chuàng)建一個 Context 對象。當(dāng) React 渲染一個訂閱了這個 Context 對象的組件,這個組件會從組件樹中離自身最近的那個匹配的 Provider 中讀取到當(dāng)前的 context 值。
- 只有當(dāng)組件所處的樹中沒有匹配到 Provider 時,其 defaultValue 參數(shù)才會生效。這有助于在不使用 Provider 包裝組件的情況下對組件進(jìn)行測試。注意:將 undefined 傳遞給 Provider 的 value 時,消費(fèi)組件的 defaultValue 不會生效。
-
*/context/index.tsimport React from 'react'; import IAppContext from './IAppContext'; import Initializer from './initializer'; // const MyContext = React.createContext(defaultValue); const AppContext = React.createContext<IAppContext>(Initializer.default); export default AppContext; -
*/context/IAppContext.tsexport default interface IAppContext { lan: string; userId: number; hotelId: number | string; isInternalUser: boolean; dyffs: KeyValuePairs<boolean>; [key: string]: any; } -
*/context/initializer.tsimport IAppContext from './IAppContext'; export default { default: { hotelId: 0, isInternalUser: false, lan: 'en-us', userId: 0, dyffs: {} }, init(): IAppContext { // @ts-ignore const ga = global.EPC && global.EPC.Logging && global.EPC.Logging.GA; const { href } = window.location; const isDev = href.search('localhost.expediapartnercentral.com') !== -1; return { hotelId: (ga && ga.resourceId) || this.default.hotelId, isInternalUser: (ga && ga.isInternalUser) || this.default.isInternalUser, lan: (ga && ga.locale) || this.default.lan, dyffs: window.EPC && window.EPC.DYFFs || {}, userId: (ga && ga.userId) || this.default.userId, }; }, };
Context.Provider
Context.Consumer
Class.contextType
Context.displayName
獲取Context的方法?
使用 Content 提供的 Consumer 組件-
使用 useContext
useContext 函數(shù)是 React Hooks 三大基礎(chǔ) hooks函數(shù)之一.import React from 'react'; import AppContext from '../../common/context'; // 獲取Context的值; const context = React.useContext(AppContext);
Examples
Attentions
1. 重復(fù)渲染問題
- 因為 context 會使用參考標(biāo)識(reference identity)來決定何時進(jìn)行渲染,這里可能會有一些陷阱,當(dāng) provider 的父組件進(jìn)行重渲染時,可能會在 consumers 組件中觸發(fā)意外的渲染。舉個例子,當(dāng)每一次 Provider 重渲染時,以下的代碼會重渲染所有下面的 consumers 組件,因為
value屬性總是被賦值為新的對象:
class App extends React.Component {
render() {
return (
<MyContext.Provider value={{something: 'something'}}>
<Toolbar />
</MyContext.Provider>
);
}
}
- 為了防止這種情況,將 value 狀態(tài)提升到父節(jié)點的 state 里:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: {something: 'something'},
};
}
render() {
return (
<Provider value={this.state.value}>
<Toolbar />
</Provider>
);
}
}
2. 不依賴 ReactDOM
- React Context 并不依賴于 ReactDOM 的 render, 也不需要放在根節(jié)點上, 它可以放在任何節(jié)點.
- 同時, 只要保證在Provider的子孫節(jié)點里面, 就可以使用 React.useContext() 或者 Context 提供的Consumer組件去取Context對象的內(nèi)容.