嗨,带你做个轻量级的状态管理

简介: Vuex 是针对 Vue2 来设计的,因为 option API 本身有很多缺点,所以 Vuex 只好做各种补丁弥补这些缺点,于是变得比较“复杂”。现在 Vue3 推出了Composition API,功能更强大也弥补了之前的缺点,但是 Vuex 4.0 只是兼容了 Vue3,使用风格上似乎没啥变化。

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 这些操作函数。

于是功能变成这个样子:


53.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 '../plugins/vueds'  // 暂时没有做成 npm 包,正在研究怎么发布。
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。对了,我只是把全局状态挂到模板上面了,局部状态没有挂呢。局部状态似乎挂不上,还需要再考虑考虑。



定义状态


/store-ds/index.js


import VuexDataState from '../plugins/vueds'
export default VuexDataState.createStore({
  global: { // 全局状态
    blogState: {
      name:'博文的状态', // 没啥用,测试的
      currentGroupId: 0, // 选择的分组ID。0:没选择
      currentArticleId: 0, // 选择的文章ID。
      editArticleId: 0 // 当前修改的文章ID
    },
    dicussState: {
      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里面去。这些都是配套工程,如果没有这些配套工程,只是做一个只读的话,总是感觉怪怪的。

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

本文作者:自然框架

个人网址:jyk.cnblogs.com

声明:本文为 脚本之家专栏作者 投稿,未经允许请勿转载。


相关文章
|
6天前
|
存储 资源调度 JavaScript
Vuex新手村指南:零基础快速入门,打造高效状态管理神器
Vuex新手村指南:零基础快速入门,打造高效状态管理神器
10 0
QGS
|
6天前
|
存储 JavaScript 容器
浅学状态管理VueX
浅学状态管理VueX
QGS
22 2
|
5月前
|
缓存 小程序 数据安全/隐私保护
微信小程序(十八)小程序维护登录态
现阶段小程序系列的最后一篇。 我们来看下小程序十如何维护登录态的,这个玩意,早应该说了,但是,直到我小程序做成现在这个状态,我也没有用到微信官方所说的使用sessionkey维护的登录态,所以,这个最后来看一下。
167 0
|
10月前
|
存储 缓存 小程序
自己动手之小程序自定义登录态维护
谈起小程序开发,想必大家都不会陌生了。众所周知,小程序开发的官方文档是要求开发者需要自行维护登录态的。那么小程序服务端开发的登录态维护大家是如何做的呢?本文适用微信和QQ小程序的自定义登录态维护。
192 0
自己动手之小程序自定义登录态维护
|
11月前
|
前端开发
前端学习笔记202303学习笔记第五天-使用key维护页面的状态
前端学习笔记202303学习笔记第五天-使用key维护页面的状态
61 0
|
JavaScript 前端开发 中间件
谈谈复杂应用的状态管理(上):为什么是 Zustand
谈谈复杂应用的状态管理(上):为什么是 Zustand
703 0
|
缓存 前端开发 JavaScript
谈谈复杂应用的状态管理(下):基于 Zustand 的渐进式状态管理实践
谈谈复杂应用的状态管理(下):基于 Zustand 的渐进式状态管理实践
278 0
|
SQL 前端开发 Java
校园外卖点餐系统——Day01【项目简介、开发环境搭建、后台系统登录和退出功能】
校园外卖点餐系统——Day01【项目简介、开发环境搭建、后台系统登录和退出功能】
170 0
校园外卖点餐系统——Day01【项目简介、开发环境搭建、后台系统登录和退出功能】
|
前端开发 JavaScript 安全
一个很简单理解的轻量级状态管理
本文适合对状态管理感兴趣的小伙伴阅读。
一个很简单理解的轻量级状态管理
|
存储 缓存 安全
【优化技术专题】「线程间的高性能消息框架」终极关注Disruptor的核心源码和Java8的@Contended伪共享指南
【优化技术专题】「线程间的高性能消息框架」终极关注Disruptor的核心源码和Java8的@Contended伪共享指南
70 0
【优化技术专题】「线程间的高性能消息框架」终极关注Disruptor的核心源码和Java8的@Contended伪共享指南