使用React Hook實(shí)現(xiàn)Redux狀態(tài)機(jī)

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,分為叫做Providerconnect來提升Redux開發(fā)效率和體驗(yàn)。

在React 16.8之前,為了實(shí)現(xiàn)統(tǒng)一的狀態(tài)機(jī),最好的方法就是使用Redux了。不過,React 16.8中Hook API新增的useContextuseReducer能夠讓我們自己實(shí)現(xiàn)一個(gè)具備Redux核心功能的狀態(tài)機(jī)。

我們先看一下Redux的三個(gè)基本原則是什么:

  1. 單一數(shù)據(jù)源 - 整個(gè)應(yīng)用程序的state存儲在單個(gè)store的對象樹中
  2. state為只讀 - 改變state的唯一方法是觸發(fā)一個(gè)action
  3. 使用純函數(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)的例子來編寫statereducer

文件目錄如下所示:

public
src
  reducer
    index.js
  index.js
  style.css
package.json

在reducer文件夾的index.js文件中,編寫initialStatereducer

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)然,我們還有像effectconnectmiddleware這樣的功能沒有完全實(shí)現(xiàn),但是在準(zhǔn)備使用他們之前,先得思考以下,是否真的需要。

Brevity is the soul of wisdom. Tediousness is the limbs and outward flourishes.

—— William Shakespeare

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容