Skip to main content

[ReactDoc] React Context API 以及 useContext Hook 的使用

imgur 圖片來源:algolia blog

基本概念與使用#

透過 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: () => {},
});
caution

預設值被使用的時機使當開發者沒有使用 <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 中可以在 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 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;

參考#

Last updated on