![]() |
圖片來源:algolia blog |
- Context @ React
- useContext @ React Hooks Reference
基本概念與使用
透過 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: () => {} });
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> ); }
示範影片與程式碼
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;
參考
- Context @ ReactJS Advanced Guide