Redux is a predictable state container for JavaScript apps.
本文的代碼可以在CodeSandbox中查看。
Redux是React經(jīng)常用到的一個(gè)數(shù)據(jù)狀態(tài)管理器(狀態(tài)機(jī)),它將應(yīng)用中所有的數(shù)據(jù)(state)以對象樹的形式存儲在store中,通過觸發(fā)action來改變state,而描繪改變規(guī)則需要編寫reducer。
因?yàn)镽edux不僅僅是為React編寫的,所以在React中常常會用到React-Redux來一同使用。React-Redux使用兩個(gè)主要的API,分為叫做Provider
和connect
來提升Redux開發(fā)效率和體驗(yàn)。
在React 16.8之前,為了實(shí)現(xiàn)統(tǒng)一的狀態(tài)機(jī),最好的方法就是使用Redux了。不過,React 16.8中Hook API新增的useContext
和useReducer
能夠讓我們自己實(shí)現(xiàn)一個(gè)具備Redux核心功能的狀態(tài)機(jī)。
我們先看一下Redux的三個(gè)基本原則是什么:
- 單一數(shù)據(jù)源 - 整個(gè)應(yīng)用程序的state存儲在單個(gè)store的對象樹中
- state為只讀 - 改變state的唯一方法是觸發(fā)一個(gè)action
- 使用純函數(shù)來修改 - 為了描述actions是如何修改state,你需要編寫reducers
我們遵循這三個(gè)基本原則來開發(fā)我們自己的狀態(tài)機(jī),其實(shí)查看useReducer
的用法就知道它已經(jīng)滿足了原則2和3
const [state, dispatch] = useReducer(reducer, initialArg, init);
所以,我們結(jié)合官網(wǎng)的例子來編寫state和reducer。
文件目錄如下所示:
public
src
reducer
index.js
index.js
style.css
package.json
在reducer文件夾的index.js文件中,編寫initialState
和reducer
:
export const initialState = { count: 0 };
export function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
throw new Error();
}
}
然后在src/index.js中生成一個(gè)store
,只在頂層組件中創(chuàng)建一個(gè)store
是為了符合原則1。
import { reducer, initialState } from "./reducer";
const store = useReducer(reducer, initialState);
可以看到應(yīng)用useReducer
很簡單的就創(chuàng)建了一個(gè)符合Redux三個(gè)基本原則的狀態(tài)機(jī),然后我們就要考慮如何將store狀態(tài)傳遞到更深層次的組件中。在Redux中我們使用subscribe
方法去訂閱狀態(tài),而使用React-Redux可以將store
像props一樣傳值給子組件,這樣就不需要每次都去訂閱。所以,接下來我們使用React中的Context API來實(shí)現(xiàn)狀態(tài)的傳遞。
在src/index.js中創(chuàng)建一個(gè)AppContext
,初始值為空:
const AppContext = createContext({});
const { Provider } = AppContext;
然后在頂層組件App
中使用:
function App() {
const store = useReducer(reducer, initialState);
return (
<Provider value={store}>
<div className="App">
<TopNavBar />
</div>
</Provider>
);
}
這樣無論多深的組件都能夠獲取到store
存儲的數(shù)據(jù)狀態(tài),而且能夠獲取到dispatch
方法來改變state
。這里React的另一個(gè)Hook就要發(fā)揮實(shí)力了,那就是useContext
,它可以接收一個(gè)context對象(React.createContext的返回值)并返回該context的當(dāng)前值。
function TopNavBar() {
const value = useContext(AppContext);
const [state] = value; // 這里的value就是useReducer創(chuàng)建的store
const { count } = state;
return (
<>
<p>{count}</p>
<Button />
</>
);
}
在Button
組件中使用useContext
獲取dispatch
來通過觸發(fā)一個(gè)action
來改變count
的值:
function Button() {
const value = useContext(AppContext);
const [state, dispatch] = value;
return (
<div className="button-wrapper">
<button type="button" onClick={() => dispatch({ type: "increment" })}>
Plus
</button>
<button type="button" onClick={() => dispatch({ type: "decrement" })}>
Minus
</button>
</div>
);
}
這樣一個(gè)滿足Redux的三個(gè)原則,同時(shí)具備React-Redux部分功能的簡潔版狀態(tài)機(jī)就完成了。在一些不需要管理過多復(fù)雜狀態(tài)的應(yīng)用中我們就可以使用這樣的方式來自己創(chuàng)建狀態(tài)機(jī),當(dāng)然,我們還有像effect、connect和middleware這樣的功能沒有完全實(shí)現(xiàn),但是在準(zhǔn)備使用他們之前,先得思考以下,是否真的需要。
Brevity is the soul of wisdom. Tediousness is the limbs and outward flourishes.
—— William Shakespeare