context api是簡化版的redux,他沒有redux強大生態體系,結合各自中間件例如thunk或saga,做data fetching或處理side effect,不過單單想存一些share data避免props drilling的問題卻綽綽有余。
- context 提供了一個無需為每層組件手動添加 props,就能在組件樹間進行數據傳遞的方法
- reducer 應是純函數,根據舊的狀態和新的參數計算出最新的狀態,其中新的參數來自於 dispatch(新的參數)
所以使用 context 還是 redux 要看需求、看成本、看以后拓展性、可維護性。(react-redux 從 v7.1.0開始也提供了 hooks api 以減少繁瑣的高階組件嵌套)
跟着 React 官網文檔,首先去看 高級指引-Context,重點理解 React.createContext(initialValue)
方法返回的 Provider 和 Consumer 組件,這是生產者和消費者模型。
知道 context 在函數式組件中基本用法后,去看 useContext,重點去感受使用了 useContext 后,代替了之前 <MyContext.Consumer>
這種寫法,令 Context 的引用變得更加方便。
接下來假設你本身已經使用過 Redux,對 Redux 那一套流程有基本的了解,Redux 本身是可以脫離 React 運行的,這是個用於全局狀態管理的庫,你當然可以結合 JQuery 來使用,但目前來看,之所以這么流行,還是因為結合了 React,所以 Redux 在 React 中主要依賴了 react-redux 這個庫。
跟着 React 官方文檔,了解 useReducer 的用法,這里也提供了 useReducer 的大概原理。
相信我,盡管可能先從其他地方開始學習 hooks,但是官方文檔絕對不要錯過。
我們模擬類似 redux 的功能,簡單分為三步:
- 使用 useReducer 在根組件創建需要共享的 state 和用來更新它的 dispatch()
- 使用 context api 把剛才的 state 和 dispatch 同時共享下去
- 使用 useContext 方便底層組件讀寫共享的 state
import React, { useContext, useReducer } from 'react';
const BankContext = React.createContext({});
// 和 redux 一樣,綜合根據舊 state 和 dispatch 而來的數據,計算出最新的 state
function reducer(state, action) {
switch (action.type) {
case 'deposit':
return { balance: state.balance + action.payload };
default:
throw new Error();
}
}
// 純粹為了展示 useReducer 第三個參數
function init(initialCount) {
return { balance: initialCount };
}
// 根組件
export default function App() {
const [state, dispatch] = useReducer(reducer, 0, init);
return (
<BankContext.Provider value={{
state,
dispatch // 把 dispatch 也作為 context 的一部分共享下去,從而在嵌套組件中調用以實現更新頂層的 state
}}>
<Layout>
<Content />
</Layout>
</BankContext.Provider>
);
}
// 子組件
function Layout(props) {
return (
<div style={{ border: '5px solid lightblue', padding: '20px' }}>
<p>您就當我是個 Layout 組件吧!</p>
{props.children}
</div>
);
}
// 孫組件
// 經過層層嵌套后,可以在孫組件中讀取全局 state 並設置
function Content() {
// 這里不要誤會,useContext(BankContext) 返回值就是我們共享出來的 context,
// 只是這里刻意把 context 設計為對象,以便同時提供 dispatch
const { state, dispatch } = useContext(BankContext);
return (
<div style={{ border: '1px solid #666' }}>
<div> 當前余額:{state.balance}</div>
<button onClick={() => dispatch({ type: 'deposit', payload: 100 })}>存入100元</button>
</div>
);
}