說實話,三大前端框架的狀態管理方案(redux、vuex、service+rxjs ),從開發體驗上來看,毫無疑問,redux 是最差的,這也致使出現了一大堆 redux 的改良品前端
但那都是別人家的輪子,出於對本身產品負責的態度,你們都不太敢隨意使用,仍是儘可能使用原生 redux ,畢竟 redux 是通過無數產品考驗過的,可靠性可以保證vue
其實咱們不須要動手從零再造個輪子,咱們只須要稍微加幾十行代碼,就能夠稍微優化一下 redux,以最低的成本提高一些開發體驗git
固然,這些優化可能在你看起來,稍顯簡陋或者並非你的痛點github
但那並不重要,本文只是提供一個最小成本優化 redux 可行的思路vuex
必定要勇於嘗試,而且要勇於去敲代碼去改變一些你以爲不合理的設計redux
按照通常的文件分類,咱們有 action 和 reducer 兩個文件前端框架
Action 和 Reducer 明顯是一一對應的,不必分爲不一樣的文件數據結構
舉個計數器的🌰子app
文件目錄結構以下框架
// 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) 時,咱們須要
此處有兩次文件的切換操做,在我看來是徹底不必的,上述 action 和 reducer 本質是改變同一個狀態(即 counter 對象),既然是改變同一個狀態,爲何不把他們和他們要改變的狀態放在一塊兒呢?
咱們能夠這麼作,以咱們須要改變的狀態 counter 爲文件名創建文件,放入對應的 action 和 reducer
文件目錄結構以下
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 風格 轉換成對象風格,將 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,
})
複製代碼
咱們能夠經過引入 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 就能夠
以下
// 改造前
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 可能存在命名衝突的問題,解決辦法是以當前文件名當作命名空間
例如,咱們有以下目錄結構
有一天咱們須要增長個 todo-list 模塊
爲了防止 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方法
複製代碼
未完待續。。。