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

未完待续。。。

相关文章
相关标签/搜索