用 useContext + useReducer 替代 redux

繆宇發表於2019-05-27

Redux 毫無疑問是眾多 React 專案首選的狀態管理方案,但我覺得 Redux 的開發體驗並不好。

比如當你正在開發一個很複雜的功能,中途需要不斷新增全域性狀態,每次新增都不得不重複如下步驟:

  1. 去到管理 redux 的資料夾,思考把這個狀態放到狀態樹的哪個位置,然後新建一個資料夾並命名 myFeature
  2. 建立三個檔案 my-feature/actions.jsmy-feature/reducer.jsmy-feature/type.js
  3. combineReducer 和並 reduce
  4. 將 action 引入到元件中
  5. 通過 connect HOC 與你的元件相連
  6. 增加兩個方法 mapStateToProps 和 mapDispatchToProps

以上只是加個狀態而已,寫很多模板程式碼還是其次,最要命的是會打斷你寫程式碼的思路。

而且隨著專案越來越大, redux 的狀態樹也會變大,維護也會變困難。

useContext + useReducer 如何替代 redux ?

useContextuseReducer 是 React 16.8 引入的新 API。

useContext:可訪問全域性狀態,避免一層層的傳遞狀態。

useReducer:用過 Redux 肯定不會陌生,它主要用於更新複雜邏輯的狀態。

下面通過一個簡單例子介紹 useContext + useReducer 是如何替代 Redux 的。

程式碼已放到 codesandbox,檢視完整程式碼

這個例子只有一個功能,點選按鈕改變字型顏色。

用 useContext + useReducer 替代 redux

開始

首先用 create-react-app 建立一個專案,也可以在 CodeSandbox 上建立一個 React App。

建立顏色展示元件 ShowArea

import React from 'react'

const ShowArea = (props) => {
  return (
    <div style={{color: "blue"}}>字型顏色展示為blue</div>
  )
}

export default ShowArea
複製程式碼

建立按鈕元件 buttons

import React from "react";

const Buttons = props => {
  return (
    <React.Fragment>
      <button>紅色</button>
      <button>黃色</button>
    </React.Fragment>
  );
};

export default Buttons;
複製程式碼

將 ShowArea 和 Buttons 匯入 index.js

import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";

import ShowArea from './ShowArea'
import Buttons from './Buttons'

function App() {
  return (
    <div className="App">
      <ShowArea />
      <Button />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
複製程式碼

狀態管理

很明顯 ShowArea 元件需要一個顏色狀態,所以我們建立 color.js 來處理狀態。

// color.js
import React, { createContext } from "react";

// 建立 context
export const ColorContext = createContext({});

/**
 * 建立一個 Color 元件
 * Color 元件包裹的所有子元件都可以通過呼叫 ColorContext 訪問到 value
 */
export const Color = props => {
  return (
    <ColorContext.Provider value={{ color: "blue" }}>
      {props.children}
    </ColorContext.Provider>
  );
};
複製程式碼

引入狀態

修改 index.js,讓所有子元件都可以訪問到顏色。

// index.js
···
···
···
import { Color } from "./color";

function App() {
  return (
    <div className="App">
      <Color>
        <ShowArea />
        <Buttons />
      </Color>
    </div>
  );
}
···
···
···
複製程式碼

獲取狀態

在 ShowArea 元件中獲取顏色

// ShowArea.js
···
···
···
import { ColorContext } from "./color";

const ShowArea = props => {
  const { color } = useContext(ColorContext);
  return <div style={{ color: color }}>字型顏色展示為{color}</div>;
};
···
···
···
複製程式碼

建立 reducer

接著在 color.js 中新增 reducer, 用於處理顏色更新的邏輯。

import React, { createContext, useReducer } from "react";

// 建立 context
export const ColorContext = createContext({});

// reducer
export const UPDATE_COLOR = "UPDATE_COLOR"
const reducer = (state, action) => {
  switch(action.type) {
    case UPDATE_COLOR:
      return action.color
    default:
      return state  
  }
}

/**
 * 建立一個 Color 元件
 * Color 元件包裹的所有元件都可以訪問到 value
 */
export const Color = props => {
  const [color, dispatch] = useReducer(reducer, 'blue')
  return (
    <ColorContext.Provider value={{color, dispatch}}>
      {props.children}
    </ColorContext.Provider>
  );
};
複製程式碼

更新狀態

為按鈕新增點選事件,呼叫 dispatch 就可以更新顏色了。

// buttons.js

import React, { useContext } from "react";
import { colorContext, UPDATE_COLOR } from "./color";

const Buttons = props => {
  const { dispatch } = useContext(colorContext);
  return (
    <React.Fragment>
      <button
        onClick={() => {
          dispatch({ type: UPDATE_COLOR, color: "red" });
        }}
      >
        紅色
      </button>
      <button
        onClick={() => {
          dispatch({ type: UPDATE_COLOR, color: "yellow" });
        }}
      >
        黃色
      </button>
    </React.Fragment>
  );
};

export default Buttons;

複製程式碼

總結

  • useContext 建立全域性狀態,不用一層一層的傳遞狀態。
  • useReducer 建立 reducer 根據不同的 dispatch 更新 state。
  • 程式碼寫到哪裡狀態就加到哪裡,不用打斷思路跳到 redux 裡面去寫。
  • 全域性狀態分離,避免專案變大導致 Redux 狀態樹難以管理。

所以 useContext + useReducer 完全可以替代 React 進行狀態管理。但是目前絕大多數 React 專案仍在使用 Redux,所以深入學習 Redux 還是很有必要的。

參考

相關文章