制作一个轻量级的状态管理插件:Vue-data-state

简介: Vuex 是针对 Vue2 来设计的,因为 option API 本身有很多缺点,所以 Vuex 只好做各种补丁弥补这些缺点,于是变得比较“复杂”。

Vuex 是不是有点繁琐?



Vuex 是针对 Vue2 来设计的,因为 option API 本身有很多缺点,所以 Vuex 只好做各种补丁弥补这些缺点,于是变得比较“复杂”。


现在 Vue3 推出了Composition API,功能更强大也弥补了之前的缺点,但是 Vuex 4.0 只是兼容了 Vue3,使用风格上似乎没啥变化。


于是乎怎么看怎么别扭,不是说 Vuex 不够强大,Vuex 的 state 也使用了 reactive ,而且也是用 provide/inject 实现注入的,但是没有后续了,Composition API 的其他特性呢?感觉好浪费呀。


虽然也可以基于 Vuex 用 compositionAPI 的方式实现功能,但是总感觉有点大炮打蚊子的感觉。


做一个轻量级状态管理



按照“自己动手丰衣足食”的原则,我们自己来做一个轻量级的状态管理。


模仿 Vuex 试着实现了一下基本功能,有点理解为啥 Vuex 弄得那么绕了,因为要把操作函数也给包含进去确实有点难度。 那么就轻量到底吧,只包含状态,不包括 mutations、action 这些操作函数。


于是功能变成这个样子:


image.png



状态:全局状态、局部状态 功能:初始化状态、指定的组件里注入局部状态、子组件里加载局部状态


缓存功能,就是可以把状态存入localstorage里面保存,以及初始化的时候从localstorage里面加载状态。 缓存功能暂时没有实现,还没想好局部状态的缓存方案。


功能简单,写起来也就容易多了,然后顺便做成插件的形式,便于使用。


// 模仿Vuex写一个简单的数据、状态、缓存管理
import { reactive, provide, inject } from 'vue'   
export default {
  // 状态容器
  store: {
    state: {}, // 全局状态
    init: () => {}, // 初始化全局状态
    reg: {}, // 注册局部状态
    get: {} // 获取局部状态
  },
  // 用 symbol 做个标识,避免重名
  storeFlag: Symbol('VuexDataState'),
  // 创建安装插件的实例
  createStore(info) {
    /* info 的结构示例
    const _info = {
      // 全局状态,在main.js里面注入
      global: {
        blogState: { // 每个状态都必须是对象,不支持基础类型
          aaa: '状态演示'
        }
      },
      // 局部状态,需要手动注入
      local: {
        dataList() {
          return {}
        }
      },
      // 初始化函数,可以从后端、前端等获取数据加入状态
      // 注入后被动调用,仅限于全局状态
      init(state) {}
    }
    */
    for (const key in info.global) {
      // 把全局状态存入state
      this.store.state[key] = reactive(info.global[key])
    }
    for (const key in info.local) {
      const localKey = Symbol(key)
      // 加上注册函数
      this.store.reg[key] = () => {
        // 把局部状态变成 reactive 的形式
        const state = reactive(info.local[key]())
        // 注入
        provide(localKey, state)
        // 返回状态,
        return state
      }
      this.store.get[key] = () => {
        // 把局部状态变成 reactive 的形式
        const state = inject(localKey)
        // 返回状态, 
        return state
      }
    }
    // 加上初始化函数
    if (typeof info.init === 'function') {
      this.store.init = info.init
    }
    const _store = this.store
    const _storeFlag = this.storeFlag
    return {
      // 安装插件
      install (app, options) {
        // console.log('install--我的状态', _store)
        // 注入状态,用 symbol 作为标记,避免重名,避免外部直接用 inject 获取
        app.provide(_storeFlag, _store)
        // 设置模板使用状态
        app.config.globalProperties.$state = _store.state
        // 调用初始化,给全局状态赋值
        _store.init(_store.state)
      }
    }
  },
  // 代码里面调用
  useStore() {
    // 获取全局状态
    const { state, reg, get } = inject(this.storeFlag)
    return {
      state, // 返回全局状态
      reg, // 注册局部状态的函数,并且返回对应的局部状态
      get // 子组件里面获取状态
    }
  }
}
复制代码


怎么样,够轻吧,不超过一百行代码,如果去掉注释空行的话,大概也就三十多行吧。


  • reactive, provide, inject


状态要实现响应性,那当然要做成 reactive 形式的。 provide、inject 实现注入功能。


  • store


内部状态容器。 state:状态 init:全局状态的初始化的函数 reg:局部状态的注入函数 get:获取局部状态的函数


  • storeFlag


用 symbol 做全局状态的标记,避免重名。是不是有一种高大上的感觉?[狗头]


  • useStore


是不是眼熟,在代码里面获取全局状态的。 除了返回全局状态外,还可以返回局部状态的注入函数和获取函数。 因为使用 symbol 作为key,外部无法获取,所以需要内部提供一个函数。(我不会告诉你我是故意的)


如果想把状态变成只读(readonlyReactive)的形式然后在返回,那么可以在这里操作。


  • 组件里的使用方法


import VueDS from 'vue-data-state' 
const { state, reg, get } = VueDS.useStore()
state // 全局状态
// 父组件注入状态,并且返回局部状态,以便父组件使用
const 局部状态 = reg.局部状态名称()
// 子组件获取局部状态
const 局部状态 = get.局部状态名称()
复制代码


  • createStore


看着是不是眼熟,功能和 Vuex 的 createStore 是一样的,接收参数创建 store 然后通过插件注入到 vue 的app上面。 函数返回 install,用于安装插件。


  • _info


这个没啥用,就是介绍一下参数的属性格式,实现代码的时候看着方便。另外去掉注释就可以做测试用。


  • 第一个for


遍历全局状态,变成 reactive 挂到 store 里面。


  • 第二个 for


遍历局部状态,变成注入和获取的函数,挂到 reg 和 get 里面。 这个 provide 的 key 也采用 symbol 的形式,避免重名。


  • init


把初始化函数挂上。


  • install


安装插件,按照 Vue 官网示例,写了这个install。 对了,我只是把全局状态挂到模板上面了,局部状态没有挂呢。 局部状态似乎挂不上,还需要再考虑考虑。


先安装资源包



yarn add vue-data-state 
复制代码


定义状态



// /store-ds/index.js
import VuexDataState from 'vue-data-state'
export default VuexDataState.createStore({
  global: { // 全局状态
    userInfo: {
      name:'当前登录人'
    }
  },
  local: { // 局部状态
    // 数据列表,使用前需要先注册
    dataListState() { // 显示博文列表用的状态
      return {
        findKind: {}, // 查询方式
        find: {}, // 查询关键字
        page: { // 分页参数
          pageTotal: 100,
          pageSize: 2,
          pageIndex: 1,
          orderBy: { id: false }
        },
        _query: {}, // 缓存的查询条件
        isReload: false // 重新加载数据,需要统计总数
      }
    }
  },
  // 可以给全局状态设置初始状态,可以是异步操作
  init(state) {
    setTimeout(() => {
      state.blogState.name = 'int里面设置的数据,可以异步'
    },3000)
  }
}) 
复制代码


  • global


全局状态,每一个状态都必须是对象(包含数组)的形式,不能是基础类型。 全局状态,会默认注入到根 app 里面。 状态名称、属性名称可以随意,这里只是举个例子。


  • local


局部状态,每个状态也必须是对象形式,不会默认注入,需要在父组件里面使用 reg 调用函数才能注入。 需要使用 return 的形式,原理和 data 一样。Vuex 模块里的 state 也是需要用 return 形式的。 状态名称、属性名称可以随意,这里只是举个例子。


  • init


初始化全局状态的函数,可以不设置。 在main.js里面安装插件时,注入全局状态后 init会被调用,这时候可以给全局状态赋值,支持异步操作。


在main.js 里面使用



这个就和 Vuex 一样了: main.js


import { createApp } from 'vue'
import store from './store-ds' // 轻量级状态
createApp(App)
  .use(store) // 轻量级状态
复制代码


后续会在个人博客里面试用一下,具体使用的时候,才会发现有没有问题,以及如何改进。


FAQ



  • 传说中的跟踪呢?


关于跟踪的问题,一直理解的不深刻,因为dev-tool总是安装不上,后来好不容易安装上了,却不工作。所以暂时跳过这个功能。


  • Vuex支持插件,你的这个呢?


这个说起来有点复杂,简单的说,目前还没有这样的需求,所以就先跳过了,以后需要的话,可以再加嘛。 要做插件的话也简单,用 watch 对状态做深度监听,然后调用插件钩子就行。 要不然我为啥要把状态拆开做reactive呢?


  • 不是说不让直接修改状态吗?


关于这一点也是比较复杂。 我可以把状态做成只读的,readonlyReactive一下就行,然后再设计 类似 mutations 的方法 来修改状态。 但是这么做的意义到底是什么呢? 没有实现跟踪功能,也没用插件,也不知道怎么弄到dev-tool里面去。 这些都是配套工程,如果没有这些配套工程,只是做一个只读的话,总是感觉怪怪的。


  • 为啥要弄个局部状态?


这个要从一次讨论说起。 某天和知乎大神聊天,他说要做一个模块内的共享状态,一开始我还不理解,讨论了半天,感觉大神说的确实在理。 于是慢慢开始尝试,最后发现确实挺香的。具体的会在后面的博客项目里面介绍。


  • 支持 option API吗?


一开始忘记这个事了,后来才想起来,因为是专门针对composition API来设计的,所以应该是不支持的吧。


源码



gitee.com/naturefw/vu…


在线演示



naturefw.gitee.io/vue-data-st…


相关文章
|
JavaScript
Vue Antdv 列表(table、list)自定义空数据状态UI
Vue Antdv 列表(table、list)自定义空数据状态UI
678 0
|
5月前
|
API
Pinia 实用教程【Vue3 状态管理】状态持久化 pinia-plugin-persistedstate,异步Action,storeToRefs(),修改State的 $patch,$reset
Pinia 实用教程【Vue3 状态管理】状态持久化 pinia-plugin-persistedstate,异步Action,storeToRefs(),修改State的 $patch,$reset
2006 1
|
6月前
|
存储 资源调度 JavaScript
|
7月前
|
存储 JavaScript API
在Vue中,如何实现状态的共享?
在Vue中,如何实现状态的共享?
165 41
|
7月前
|
缓存 JavaScript 前端开发
Vue状态管理:请解释Vue中的异步组件加载是如何工作的?
Vue的异步组件通过`Vue.component()`实现,它接受组件配置、名称和回调函数。回调可返回Promise或IIFE以按需加载组件定义,提高性能。
32 0
|
7月前
|
数据可视化 JavaScript
Vue-cli可视化界面的bulid构建线上模式打包失败:No module factory available for dependency type: CssDependency
Vue-cli可视化界面的bulid构建线上模式打包失败:No module factory available for dependency type: CssDependency
|
7月前
|
存储 JavaScript 前端开发
Vue状态管理:Vue中的状态管理是什么?为什么使用状态管理?
Vue状态管理:Vue中的状态管理是什么?为什么使用状态管理?
368 2
|
JavaScript
Vue框架学习(第十三课)Vuex状态管理中的store和state属性
Vue框架学习(第十三课)Vuex状态管理中的store和state属性
188 0
|
开发工具 git
从0搭建Vue3组件库(八):使用 release-it 实现自动管理发布组件库
从0搭建Vue3组件库(八):使用 release-it 实现自动管理发布组件库
193 0
|
JavaScript 前端开发 数据安全/隐私保护
从0搭建vue3组件库: Input组件(上)
从0搭建vue3组件库: Input组件(上)
187 0
下一篇
DataWorks