制作一个轻量级的状态管理插件: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…


相关文章
|
Cloud Native Devops 持续交付
云原生技术:企业数字化转型的引擎
【5月更文挑战第31天】随着信息技术的飞速发展,云计算已经成为了企业数字化转型的重要推动力。本文将深入探讨云原生技术的概念、优势以及在企业中的应用,帮助企业更好地理解和利用这一技术,实现数字化转型。
|
前端开发
什么是HTML?
什么是HTML?
|
消息中间件 运维 算法
【DSW Gallery】OneClassSVM 算法解决异常检测问题
OneClassSVM 是一种无监督的异常检测算法, 用于对无 label 的数据进行异常检测,并且支持将 OneClassSVM 模型部署成一个流服务,用来对实时数据进行异常检测。该D emo 将介绍如何在 DSW 中使用 OneClassSVM 算法解决异常检测问题。
【DSW Gallery】OneClassSVM 算法解决异常检测问题
|
运维 数据安全/隐私保护 开发者
Dataphin权限体系(5):自定义项目角色【Dataphin V3.8】
在讨论权限体系的时候,角色是一个绕不开的概念,并且大多数情况下我们的权限都是由不同的角色来承载。Dataphin对开发过程的权限管控,主要也是通过项目来划分成员和资源,通过项目角色来配置不同的权限,因此,Dataphin内置了众多的项目角色,用于管理者进行权限管理。但是在不同客户的多样化的管控场景中,内置的固定角色偶尔会无法满足客户的管理诉求,这种时候就需要用到自定义项目角色的功能,来定制符合自己企业管理要求的角色。
Dataphin权限体系(5):自定义项目角色【Dataphin V3.8】
|
NoSQL 数据库 索引
mongo索引命令
http://blog.csdn.net/salmonellavaccine/article/details/53907535 1. 创建/重建索引 MongoDB全新创建索引使用ensureIndex()方法,对于已存在的索引可以使用reIndex()进行重建。
1131 0
|
安全 网络安全 数据安全/隐私保护
|
算法 调度 安全
设备管理
  文件系统实现了逻辑文件和物理文件的转换,但在实现这种转换时,必须要对外围设备进行启动和控制。这一功能是由操作系统的设备管理部分来实现的。所以设备管理与文件系统密切相关。文件系统确定了文件应该怎样转换以及确保文件的安全使用,而设备管理实现文件信息在存储介质与主存储器之间的传送。
1129 0
|
5天前
|
弹性计算 人工智能 安全
云上十五年——「弹性计算十五周年」系列客户故事(第二期)
阿里云弹性计算十五年深耕,以第九代ECS g9i实例引领算力革新。携手海尔三翼鸟、小鹏汽车、微帧科技等企业,实现性能跃升与成本优化,赋能AI、物联网、智能驾驶等前沿场景,共绘云端增长新图景。
|
11天前
|
存储 弹性计算 人工智能
【2025云栖精华内容】 打造持续领先,全球覆盖的澎湃算力底座——通用计算产品发布与行业实践专场回顾
2025年9月24日,阿里云弹性计算团队多位产品、技术专家及服务器团队技术专家共同在【2025云栖大会】现场带来了《通用计算产品发布与行业实践》的专场论坛,本论坛聚焦弹性计算多款通用算力产品发布。同时,ECS云服务器安全能力、资源售卖模式、计算AI助手等用户体验关键环节也宣布升级,让用云更简单、更智能。海尔三翼鸟云服务负责人刘建锋先生作为特邀嘉宾,莅临现场分享了关于阿里云ECS g9i推动AIoT平台的场景落地实践。
【2025云栖精华内容】 打造持续领先,全球覆盖的澎湃算力底座——通用计算产品发布与行业实践专场回顾