[React Hooks長文總結系列三]隨心所欲,製做「窮人版」的redux

前言

在離職以後,我開始靜下心來,思考原來在繁重的業務開發節奏中無暇思考的一些問題,本期的主題是純函數鉤子useReducer和共享狀態鉤子useContext前端

什麼是reducer函數?

在react中,reducer函數是一個很重要的概念。它表示一個接收舊狀態,返回新狀態的函數。vue

const nums = [123]
const value = nums.reduce((acc, cur) => acc + cur, 0)

在上述例子中,reduce函數的一個參數,就是一個標準的reducer函數。react

在以前的setState使用中,你可能會好奇setNum(prev => prev + 1)prev怎麼來的,讓咱們深刻到最底層看看吧,實際上useState並不是最底層的元素,它內部仍然用到了useReducer來實現,在react源碼中有個basicStateReducer,大體結構以下:web

function basicStateReducer(state, action{
  return typeof action === 'function' ? action(state) : action;
}

因此,當咱們的setter接收的參數是一個函數時,舊的state將做爲參數被該函數使用。vuex

useReducer

useReducer基本用法以下:數據庫

const [state, dispatch] = useReducer(reducer, initialState, initFunc);

其中第三個參數是可選參數,咱們通常只會用到前兩個。redux

一個簡單的示例(實現數字+1)以下:編輯器

import React, { useReducer } from "react";

const initialState = {
  num0,
};

function reducer(state, action{
  switch (action.type) {
    case "increase":
      return { num: state.num + 1 };
    case "decrease":
      return { num: state.num - 1 };
    default:
      throw new Error();
  }
}

const NumsDemo = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      <h1>數字爲: {state.num}</h1>
      <button onClick={() => dispatch({ type: "increase" })}>+</button>
      <button onClick={() => dispatch({ type: "decrease" })}>-</button>
    </>

  );
};

export default NumsDemo;

你可能也發現了,useReduceruseState 很是相似:定義狀態和修改狀態的邏輯useReducer 用起來更加麻煩,可是若是須要維護的狀態自己比較複雜,多個狀態之間相互依賴,那麼 useReducer的好處才真正顯示出來了:用一個語義化的action來隱藏修改狀態的複雜細節。ide

useContext

useContext則比較簡單,它用於在局部組件樹中共享數據,有點相似於vue中的provide/inject,基本使用以下:函數

// 在模塊的入口文件中定義
// Home.tsx
export const MyContext = React.createContext({num24});

// 在模塊內部的某個組件中獲取
// Sub.tsx
import MyContext from '../Home';

const Sub = () => {
  const state = useContext(MyContext);
  // ...
}

redux的基本理念以及解決了什麼問題

好了,如今咱們已經肯定要弄一個小型redux了,不過在這以前咱們仍是須要回顧一下redux的基本理念。

在對於小型頁面共享數據時,咱們通常會有諸如「狀態提高」這樣的開發約定,也就是說,咱們會將共享的狀態放到上層最近的公共父級。可是當頁面數量一多,組件拆分粒度變細,這種「共享狀態」的機制變得很脆弱,很冗餘。

redux 的機制就是爲了解決這個問題,redux 有幾個很是明顯的特色:

  1. 數據的惟一真相來源;
  2. reducer 純函數;
  3. 單項數據流。

redux 的單項數據流,能夠歸納爲三個部分:ViewReducersStore

View視圖層發起更新動做(dispatch),會抵達更新函數層(Reducers),更新函數執行並返回最新狀態,抵達狀態層(Store),狀態層更新後將通知視圖層更新。

reduxredux

其實我以爲,不管是 vuex 也好,redux 也好,它的設計理念都是相似一個「前端數據庫」。在store層應該只存放公共狀態,不建議存放其餘的東西,好比公共方法,由於這與reducer純函數的理念是相悖的。

實現一個簡單的小型redux

好了,讓一切開始吧,咱們這裏只定義三個組件:根組件App,第一個子組件Sub1,第二個子組件Sub2。實現一個很是簡單的數字加減功能,以下:

// 說明:爲了代碼更加易讀,已經將ts的類型定義作了刪除操做
// App.tsx
import React, { useReducer } from "react";
import Sub1 from "./Sub1";
import Sub2 from "./Sub2";

const INITIAL_STATE = {
  name"zhang",
  age24,
};

// 定義改變狀態的幾種操做
function reducer(state, action{
  switch (action.type) {
    case "increaseAge":
      return { ...state, age: state.age + 1 };
    case "decreaseAge":
      return { ...state, age: state.age - 1 };
    case "changeName":
      return { ...state, name: action.payload };
    default:
      return state;
  }
}

// 選擇性導出該context
export const AppContext = React.createContext(null);

function App() {
  const [state, dispatch] = useReducer(reducer, INITIAL_STATE);

  return (
    <AppContext.Provider value={{ statedispatch }}>
      <Sub1 />
      <Sub2 />
    </AppContext.Provider>

  );
}

export default App;


// 在Sub1.tsx中
import React, { useContext } from "react";
import { AppContext } from "./App";

const Sub1 = () => {
  const { state, dispatch } = useContext(AppContext);
  return (
    <>
      <h1>Sub1年齡爲: {state.age}, 名字爲:{state.name}</h1>
      <button onClick={() => dispatch({ type: "increaseAge" })}>+</button>
    </>

  );
};

export default Sub1;

// 在Sub2.tsx中
import React, { useContext } from "react";
import { AppContext } from "./App";

const Sub2 = () => {
  const { state, dispatch } = useContext(AppContext);
  return (
    <>
      <h1>Sub2年齡爲: {state.age}, 名字爲:{state.name}</h1>
      <button onClick={() => dispatch({ type: "decreaseAge" })}>-</button>
    </>

  );
};

export default Sub2;

運行以上示例,能夠發如今一處子組件更改公共狀態,其餘消費到該公共狀態的組件(Consumer)都會更新。這有效避免了隔代傳props所引起的代碼臃腫脆弱問題。

useReducer + useContext 可否代替 redux?

不能,我在項目中雖然已經這麼用了,可是仍是發現對比redux的功能是有所欠缺的,其中最典型的就是更新公共狀態後沒有回調的問題。

useReducer + useContext其實是製造了一個「窮人版的 redux」。並且咱們必須花費額外的心思去避免性能問題(React.memouseCallback等),然而這些煩人的 dirty works 其實 React-Redux 等工具已經默默替咱們解決了。除此以外,咱們還會面臨如下問題:

  • 須要自行實現 combineReducers 等輔助功能
  • 失去 Redux 生態的中間件支持
  • 失去 Redux DevTools 等調試工具
  • 出了坑不利於求助……

以上四個坑點摘抄於騰訊的這篇文章,仔細讀完後發現確實寫的能夠:Redux with Hooks

相關文章
相關標籤/搜索