[ReactDoc] React Context API 以及 useContext Hook 的使用
圖片來源:algolia blog
- Context @ React
- useContext @ React Hooks Reference
- How to use React context effectively @ KCD
基本概念與使用
透過 React Context API 可以將資料傳到直接傳送到需要的元件,而不需要手動一直透過 props
傳入:
- Context 是設計來在 React 元件中共享資料,類似在 React 元件的全域(global),這些資料類似「使用者的登入狀態」、「樣式(theme)」、「檢視語言(preferred language)」、「資料快取(cache)」。
- Context 主要用在當多個不同嵌套層級的元件要用到相同的資料時才會只用,除此之外,應該盡量避免使用,因為它會使得元件更難被復用(reuse)。
如果你只是想要避免一直傳遞 props 到每一層, component composition 通常是一個更簡單的方式。
Context 的使用方式
讀取資料:Context 的方式是在根元件的地方(例如,App.js
)透過 <Context.Provider>
把需要傳遞的資料帶入 Context
中,而這些資料通常會放在根目錄的 state
中;接著,在需要使用到此資料的地方,在透過 contextType
取得 context 內的資料。
修改資料:若有需要修改 Context 中的內容,則是去修改根元件的 state
後重新帶入 <Context.Provider>
中,如此子層的元件就可以再次透過 contextType
取得新的資料,因此把修改 state
的函式放在 state
中是很常見的一種做法。
import { MyContext } from './src/contexts'; // 透過 React.createContext 建立
class Foo extends React.Component {
constructor() {
this.state = {
size: '2x',
changeSize: (size) => this.setState({ size }),
};
}
render() {
return (
<MyContext.Provider value={this.state}>
<App />
</MyContext.Provider>
);
}
}
🔖 所以實際上真正操作資料的地方是在根元件的,而 Context 更像是修改和傳遞資料的一個媒介而已。
STEP 0:建立 SwitchThemeButton 元件
// ./src/ThemedButton
import React from 'react';
class ThemedButton extends React.Component {
render() {
return <button>Change Theme</button>;
}
}
export default ThemedButton;
STEP 1:建立 Context
React.createContext() @ React
透過 Context API 可以不用透過 props 一直將資料傳到各元件內,透過 React.createContext
API 建立一個 context,裡面的內容為預設值:
// ./src/AppContext.js
// STEP 1: createContext(<預設值>)
export const AppContext = React.createContext({
theme: 'light',
toggleTheme: () => {},
});
預設值被使用的時機是當開發者沒有使用 <Context.Provider>
卻又使用了 useContext
去取值時會用到;一旦使用 Context.Provider 後,就會以 <Context.Provider value={} />
中 value
帶入的值為主。
STEP 2:使用 Context.Provider
Context.Provider @ React
透過 Context.Provider
中的 value
屬性,可以把想要的值傳入內部的每個元件,:
// ./src/App.js
// ...
import { AppContext } from './AppContext';
class App extends React.Component {
constructor(props) {
super(props);
// 定義修改 state 的方法
this.toggleTheme = () => {
this.setState(prevState => ({
theme: prevState.theme === 'dark' ? 'light' : 'dark',
}))
}
// 在 state 中放入修改 state 的方法,並傳入 <Context.Provider> 中
this.state = {
theme: 'dark',
toggleTheme
}
}
render() {
// STEP 2: Use Context.Provider
// 將 state 的資料放入 Provider 中
return (
<AppContext.Provider
value={this.state}
>
<Toolbar />
</AppContext.Provider>
);
}
⚠️ 因為
value
內如果放物件的話,每次都算是全新的物件,因此為了避免經常重新轉譯,可能的話將Context.Provider
屬性value
內的值放在state
中,使用value={this.state}
這種做法。
STEP 3:定義 contextType 以取得 context 內容
Class.contextType @ React
若想要在元件的個生命週期中使用 this.context
取得 Context 的值,需要先將該元件定義 contextType
,React 會找在與該元件最接近的 Context Provider,並且將可以在 render 時取用到它的值。定義 contextType
的方法有兩種:
// ./src/ThemedButton.js
// ...
import { AppContext } from './src/AppContext';
class ThemedButton extends React.Component {
// STEP 3: 方法一,透過 static 定義 contextType
static contextType = AppContext; // 才可以使用 this.context
render() {
// 在 render 中將可以使用 this.context
const { theme, toggleTheme } = this.context;
return (
<button theme={theme} onClick={toggleTheme}>
{theme}
</button>
);
}
}
// STEP 3: 方法二 定義 contextType
ThemedButton.contextType = AppContext; // 才可以使用 this.context
在 Functional Component 中使用 Context 的值
Context.Consumer @ React
如果 functional component 需要使用 Context 的值,可以透過 Context.Consumer
元件,其內部需要帶入 function,該 function 的參數即可取得 Context 的值:
/*
<AppContext.Consumer>
{(value) => { ... }}
</AppContext.Consumer>
*/
function Toolbar(props) {
return (
<div>
<AppContext.Consumer>
{({ theme, size }) => (
<p>
theme: {theme} <br />
size: {size}
</p>
)}
</AppContext.Consumer>
<ThemedButton />
</div>
);
}
示範影片與程式碼
示範程式碼 @ CodeSandbox
React Hooks - useContext
- useContext @ React Doc
- The Guide to Learning React Hooks - Hooks For Context @ KendoReact
useContext
中可以在 function 中使用 useContext(MyContext)
,這等同於在 class 中使用 static contextType = MyContext
,或者是 <MyContext.Consumer>
的用法。
import { MyContext } from './src/contexts';
const value = useContext(MyContext);
若把上面範例中的 ThemedButton
元件改成 useContext
的寫法,則會變成:
import React, { useContext } from 'react';
import { ThemeContext } from './../contexts/ThemeContext';
const SwitchThemeButton = () => {
const context = useContext(ThemeContext);
const { theme, toggleTheme } = context;
return (
<button
style={{
color: theme.foreground,
backgroundColor: theme.background,
}}
onClick={toggleTheme}
>
Change Theme
</button>
);
};
export default SwitchThemeButton;
其他
- 如果有需要使用多個 Context,參考 Consuming Multiple Contexts。
沒有使用 context API 的情況
class App extends React.Component {
render() {
return <Toolbar theme="dark" />;
}
}
const Toolbar = (props) => {
// 這裡 Toolbar 元件必須有多一個額外的 "theme" prop,並帶入到 ThemedButton 中
// 然而,如果每一個在 App 內的按鈕都需要知道 theme 的話,這樣傳遞資料會變得非常麻煩
return (
<div>
<ThemedButton theme={props.theme} />
</div>
);
};
class ThemedButton extends React.Component {
render() {
return <button>{this.props.theme}</button>;
}
}
使用 context API 後
要使用 Context Value 的地方要先將該 class 定義 contextType
,如此才可以在各個生命週期使用 this.context
取得 Context 的內容。定義的方法包括:
const AppContext = React.createContext({
theme: 'light',
size: '2x',
});
// 定義 contextType
class ComponentUseContext extends React.Component {
static contextType = AppContext; // 才可以使用 this.context
}
// 如果在 class 是沒寫 static contextType = AppContext;
// 則需要額外在這定義 contextType
ThemedButton.contextType = AppContext; // 才可以使用 this.context
完整程式內容:
import React from 'react';
import logo from './logo.svg';
import './App.css';
// STEP 1: createContext(<defaultValue>)
// 透過 Context API 可以不用透過 props 一直將資料傳到各元件內
// 建立一個 context,並以 light 為預設值
const AppContext = React.createContext({
theme: 'light',
size: '2x',
});
class App extends React.Component {
render() {
// STEP 2: Use Context
// 使用 Provider 可以將當前的 theme 傳入內部的每個子元件
// 每個元件都能讀取到它,不論它有多深
return (
<AppContext.Provider
value={{
theme: 'dark',
size: '1x',
}}
>
<Toolbar />
</AppContext.Provider>
);
}
}
// 中間層的元件不需要做任何事
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// STEP 3: 取得 AppContext 的值
// 指定 contextType 來讀取當前的 AppContext 的值
// React 會找在 AppContext 的 Provider 最接近的那個,並使用它的值
// 這裡會是 "dark"
static contextType = AppContext; // 才可以使用 this.context
render() {
const { theme, size } = this.context;
return (
<button theme={theme} size={size}>
{theme}, {size}
</button>
);
}
}
// 如果在 class 是沒寫 static contextType = AppContext;
// 則需要額外在這定義 contextType
ThemedButton.contextType = AppContext; // 才可以使用 this.context
export default App;
常見的 Pattern
React with TypeScript > useContext @ pjchender.dev
參考
- Context @ ReactJS Advanced Guide