延續昨天的介紹,Context 除了透過傳給 Provider 不同的 value
值之外,也可以透過傳入 state 後,用 state 的更新去改變裡面的值。今天就會來介紹如何使用之前介紹的 reducer,搭配 context 讓 state 變成 global 的,可以跳過中間的 component 來進行使用。
今天的文章參考官方文件的:
要讓 reducer 跟 context 結合讓 tree 裡的 component 都能使用 reducer 裡的內容,一樣會有幾個步驟:
1. 建立 Context
記得 useReducer
會回傳提供資訊的 state
跟用來更新 state 的 dispatch
嗎?為了讓這兩個功能都能在 tree 裡自由的使用,我們通常會開兩個 context 去管理這兩個功能:
import { createContext } from 'react';
export const TasksContext = createContext(null);
export const TasksDispatchContext = createContext(null);
一個去管理 state 資料 (TasksContext
or TasksStateContext
),另一個就專心處理更新 state 邏輯 (TasksDispatchContext
)。當然也是可以只使用一個 context 去管理,但這樣可能就要傳入一個 object 去維護,在拆解的時候可能就會讓程式碼變得混淆複雜。另外就是有的時候 component 會只需要更新的 function,而不需要 state 資訊,分開 context 可以減少一些效能的使用。
2. 建立 Provider
有了 context 之後,接下來就會需要給 Provider 值讓 tree 裡的 component 使用,要搭配 useReducer
的話就會是要這樣寫:
export default function TaskProvider() {
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
// ...
return (
<TasksContext value={tasks}>
<TasksDispatchContext value={dispatch}>
...
</TasksDispatchContext>
</TasksContext>
);
}
直接把 useReducer
回傳的值分別分派給 TasksContext
跟 TasksDispatchContext
。這邊其實也可以不使用 useReducer
而使用 useState
,當然直接用 Immer 也是沒問題的,context 的運用十分自由。
3. 在 tree 裡的 Component 使用 context
有了 Provider 之後,我們就可以用 Provider 去包我們想要使用 context 的 components:
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
import { TasksProvider } from './TasksContext.js';
export default function TaskApp() {
return (
<TasksProvider>
<h1>Day off in Kyoto</h1>
<AddTask />
<TaskList />
</TasksProvider>
);
}
包住之後裡面的 component 就可以透過使用 useContext
去取得想要的功能:
const tasks = useContext(TasksContext);
const dispatch = useContext(TasksDispatchContext);
通常我們在使用這樣的 context 會一起寫進一個檔案裡,裡面就會有 context 宣告 (createContext
)、Provider (跟著 useReducer
) 跟 useContext
使用,在裡面就可以多宣告 客製化的 hooks (Custom Hooks),讓我們可以有更好辨識的命名去使用 context:
export function useTasks() {
return useContext(TasksContext);
}
export function useTasksDispatch() {
return useContext(TasksDispatchContext);
}
這樣就能清楚知道想要使用 tasks
這個 state 資訊就用 useTasks
,想要更新 tasks
就使用 useTasksDispatch
。
有時候當 components 越設越多,使用 context 也可能會不小心用在沒有包含在 Provider 裡面的 components,就可能讓 useContext
抓不到正確的值。這時候我可以寫一點防呆在我們剛剛的客製化 hooks:
export function useTasks() {
const context = useContext(TasksContext);
if (!context) {
throw new Error('Error(useTasks): Component is not in TasksProvider!');
}
return context;
}
export function useTasksDispatch() {
const context = useContext(TasksDispatchContext);
if (!context) {
throw new Error('Error(useTasksDispatch): Component is not in TasksProvider!');
}
return context;
}
這樣就會在抓不到 context 的時候回傳錯誤給開發者知道。
state 跟 context 的結合就介紹到這邊,其實像 context 這樣處理的 global state management 有很多外部套件可以參考使用,像是 React Redux 或是 Zustand 等 packages 都可以幫助我們進行一些進階的操作,大家也可以根據需求選取最適合自己的工具,如果不想多引用外部套件也可以繼續使用 context 也是其中一個方法。
今天的介紹就先到這邊,感謝大家耐心地看完,如果有任何問題與建議歡迎告訴我,明天見,晚安。