10分鐘讓你的Redux更好用

前言

說實話,三大前端框架的狀態管理方案(redux、vuex、service+rxjs ),從開發體驗上來看,毫無疑問,redux 是最差的,這也致使出現了一大堆 redux 的改良品前端

但那都是別人家的輪子,出於對本身產品負責的態度,你們都不太敢隨意使用,仍是儘可能使用原生 redux ,畢竟 redux 是通過無數產品考驗過的,可靠性可以保證vue

其實咱們不須要動手從零再造個輪子,咱們只須要稍微加幾十行代碼,就能夠稍微優化一下 redux,以最低的成本提高一些開發體驗git

固然,這些優化可能在你看起來,稍顯簡陋或者並非你的痛點github

但那並不重要,本文只是提供一個最小成本優化 redux 可行的思路vuex

必定要勇於嘗試,而且要勇於去敲代碼去改變一些你以爲不合理的設計redux

正文

官方推薦 action 和 reducer 放在不一樣文件,可是這樣致使文件切換繁瑣

按照通常的文件分類,咱們有 actionreducer 兩個文件前端框架

Action 和 Reducer 明顯是一一對應的,不必分爲不一樣的文件數據結構

舉個計數器的🌰子app

文件目錄結構以下框架

  • store
    • action.js
    • reducer.js
// action.js
export const ADD ='add'

//reducer.js
const counter ={ count: 0 }

export default function reducer(state = counter, action) {
  switch (action.type) {
    case 'ADD':
      return {
        ...state,
        count:state.count+1
      }
      break;

    default:
      return state
      break;
  }
}

//組件使用
import {ADD} from './action.js'

store.dispatch(ADD)
複製代碼

當咱們想要查看組件邏輯 store.dispatch(ADD) 時,咱們須要

  1. 打開 action 文件,搜索常量 ADD
  2. 打開 reducer 文件,搜索常量 ADD

此處有兩次文件的切換操做,在我看來是徹底不必的,上述 action 和 reducer 本質是改變同一個狀態(即 counter 對象),既然是改變同一個狀態,爲何不把他們和他們要改變的狀態放在一塊兒呢?

咱們能夠這麼作,以咱們須要改變的狀態 counter 爲文件名創建文件,放入對應的 action 和 reducer

文件目錄結構以下

  • store
    • counter.js

counter.js 內容以下

const initialState = {
  count: 10
}

function reducer(state = counter, action) {
  switch (action.type) {
    case 'add':
      return {
        ...state,
        count:state.count+1
      }
      break;

    default:
      return state
      break;
  }
}

複製代碼

Reducer switch 寫法較爲繁瑣

當 reducer 較多時,使用 switch 較爲繁瑣

咱們能夠寫個工具方法 將 reducer switch 風格 轉換成對象風格,將 action 轉換成對象的屬性名

//工具方法
const handleActions = ({ state, action, reducers}) =>
  Object.keys(reducers)
    .includes(action.type)
    ? reducers[action.type](state,action)
    : state


//counter.js
const initialState = {
  count: 10
}

const reducers = {
  add(state, action) {
    return{
      ...state,
      count:state.count+1
    }
  },
  minus(state, action) {
    return{
      ...state,
      count:state.count-1
    }
  },
}

export default (state = initialState, action) => handleActions({
  state,
  action,
  reducers,
})

複製代碼

reducer 必須是純函數,必須返回新的引用,當對象層次較深時,寫法繁瑣

咱們能夠經過引入 immer(一個小巧的不可變數據結構的庫) 來優化

第一步 引入 immer

import produce from "immer"
複製代碼

第二步 修改 handleActions 工具函數

export const handleActions = ({ state, action, reducers}) =>
  Object.keys(reducers)
    .includes(action.type)
    ? produce(state, draft => reducers[action.type](draft, action))
    : state

//新增了這一行
produce(state, draft => reducers[action.type](draft, action))
複製代碼

而後咱們寫 reducer 就能夠

  1. 不須要每次手動return
  2. 不須要手動生成新的引用

以下

// 改造前
const reducers = {
  add(state, action) {
    return{
      ...state,
      count:state.count+1
    }
  },
  minus(state, action) {
    return{
      ...state,
      count:state.count-1
    }
  },
}
//改造後
const reducers = {
  add(state, action) {
    state.count++
  },
  minus(state, action) {
    state.count--
  },
}
複製代碼

稍微解釋一下咱們新增的這行代碼

produce(state, draft => reducers[action.type](draft, action))
複製代碼

這裏涉及到 immer 的使用

produce 的第一個參數是你想操做的對象,咱們這裏是操做 state

第二個參數是一個函數,immer 會給該函數傳個參數,參數爲你操做的對象的副本(能夠理解爲深拷貝對象),對該副本進行操做時不會影響原對象

因此咱們在 reducers 對象內寫的函數就至關於寫 produce 的第二個參數,同時在 handleActions 工具函數內,咱們經過三元表達式也已經 return了,也就實現了 reducers 對象內的函數不須要手動 return

增長命名空間

當項目大了後,咱們寫的 action 可能存在命名衝突的問題,解決辦法是以當前文件名當作命名空間

例如,咱們有以下目錄結構

  • store
    • counter.js

有一天咱們須要增長個 todo-list 模塊

  • store
    • counter.js
    • todoList.js

爲了防止 counter 和 todoList 內 action 命名衝突,咱們能夠改造下 handleActions 工具函數

import produce from "immer"

const getKey = (str, flag) => {
  const i = str.indexOf(flag)
  return str.substring(i + 1, str.length + 1)
}

export const handleActions = ({ state, action, reducers, namespace = '' }) =>
  Object.keys(reducers)
    .map(key => namespace + '/' + key)
    .includes(action.type)
    ? produce(state, draft => reducers[getKey(action.type, '/')](draft, action))
    : state

export default (state = initialState, action) => handleActions({
  namespace: 'counter',//增長命名空間
  state,
  action,
  reducers,
})
複製代碼

組件這樣使用

store.dispatch('counter/add')//counter 模塊的 add方法
store.dispatch('todoList/add')//todoList 模塊的 add方法
複製代碼

示例代碼

待優化的點

  • action 基於字符串,編輯器沒法作到智能提示,而且容易出現拼寫錯誤
  • 支持ts

未完待續。。。

相關文章
相關標籤/搜索