Why Context
在寫 React 網站的時候,因為資料都是由上而下的藉由元件傳遞,有些情況,例如:整體 UI 主題、使用者身份驗證、偏好語言...等廣泛應用的資料,就會顯得太笨重。同樣的資料要同時傳遞給很多下層元件、而且這些元件可能都在深處,會讓 code 更加凌亂且難以閱讀。對此,React 提供了一種方法:Context,讓資料的分享可以不需要如此繁複。
How To Use Context
Context 的設計理念是用來分享那些在元件樹中,稱得上 global 的資訊。例如下面的情境,就是當使用者,點選了「黑夜版」的網頁:
// 使用 createContext 方法,為目前的網站色調建立 context,並給予一個預設值
const ThemeContext = React.createContext('morning');
class App extends React.Component {
render() {
// 使用 Provider 方法,把上面建立的 context 傳遞給底下的元件,這裏重新賦予新的黑夜版樣式
// 不論元件埋的多深,都可以讀到
return (
<ThemeContext.Provider value="night">
<Menu />
....裡面的 component 都可以收到
</ThemeContext.Provider>
);
}
}
// Menu 作為傳遞的中間人,不需要有把 props 往下傳的行為
function Menu() {
return (
<div>
<MenuButton />
<MenuButton />
<MenuButton />
</div>
);
}
class MenuButton extends React.Component {
// 要閱讀到我們一開始定義的 ThemeContext,需要把它賦值給 contextTyp
// 這裡的運作是這樣子:React 會去找到最接近且吻合的 theme provider 並使用它的值(也就是'night')
static contextType = ThemeContext;
render() {
return <Button color={this.context} />;
}
}
你要傳遞下去的資訊,也有可能是一大包,收攏在另外的文件裡,如以下的案例,此時,可以這樣傳遞:
///// theme 的定義區塊 theme.js
export const themes = {
morning: {
fontColor: 'black',
background: 'light-grey',
q
},
night: {
fontColor: 'white',
background: 'blue',
},
};
export const ThemeContext = React.createContext(
themes.night
);
///// theme 的使用者 footer.js
import {ThemeContext} from './theme-context';
class ThemeFooter extends React.Component {
render() {
let props = this.props;
let theme = this.context;
return (
<Footer
{...props}
style={{backgroundColor: theme.background}}
/>
);
}
}
// 這裏把 import 進來的樣式表,塞進元件的 contextType 裡
ThemeFooter.contextType = ThemeContext;
export default ThemeFooter;
///// theme 的切換區塊 app.js
import {ThemeContext, themes} from './theme';
import ThemeFooter from './footer';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
theme: themes.morning,
};
}
render() {
return (
<Page>
<ThemeContext.Provider value={this.state.theme}>
</ThemeFooter>
//....其他需要使用樣式的元件
</ThemeContext.Provider>
</Page>
);
}
}
ReactDOM.render(<App />, document.root);
什麼情況下不該用 Context ?
過度的使用 context,會讓你的元件變得不好複用,如果你單純只是討厭把資料傳遞很多層(而非那些廣泛運用的情境),可以考慮別的作法。以下舉一個例子:假設網站的 照片、使用者名稱、性別... 在最上層取得,需要經過頁面 layout、Header、Menu 區域...才能抵達最後會用到資訊的 使用者頭像 元件,除了層層傳遞或者用 context(不建議),還可以如何解決?
解決方法之一是,不要單獨傳遞 props 資訊下去,你可以直接傳遞一整個使用者頭像元件,中間用來傳遞的元件,就不需要看到那些細碎的使用者資料。
// 這裡是最上層取得使用者資訊的地方
function Page(props){
// 把使用者頭像在上層,連同要用到的資訊打包好
const userAvatar = <Avatar user={user} size={props.avatarSize} />;
// 再把 使用者頭像元件 當成 props 傳遞下去
return <PageLayout userAvatar={userAvatar} />;
}
然而,這樣的做法也並非適用於每個案例,當我們把 userAvatar 在上層打包好,也意味著會增加上層元件的複雜程度。
這樣子的 props 並不侷限於只能應用一組資料,你也可以為要傳遞下去的 props 建立很多個不同的「插槽」(如範例)。如果你傳遞下去的這些元件,有與上層元件溝通的需求,你可以考慮 Render Props 的方法。
function Page(props){
const blockContent = <Description user={user} />;
const header = <Avatar user={user} size={props.avatarSize} />;
const detail = <Detail infor={props.infor} />;
return (
<PageLayout
header={header}
content={blockContent}
moreInfor={detail}
/>
);
}
以上的案例情境都較為單純,但實際在使用 context 的時候,情況可能更加複雜,有時候會有多組的 context 需要傳遞,或者 theme toggle 也需要一併傳遞到子元件的情況...,請見下篇文章。