关于 Vue3 Store 的两种写法

轩陌

分类: Vue 589 0

前言

因为 Vue3 的组合式 API 的出现,结合我自己的一些使用,分享一下我的个人看法;因为有了组合式 API 的出现,让代码的组装更灵活了,数据管理可拆分,所以有了 class 写法和 pinia,我个人主要是 React + Vue 都写,公司用 React,个人的项目、作品用 Vue3,也做了一套 Vue3 的组件库 https://github.com/D-xuanmo/dl-ui

两种写法

目前 Vue 官网推荐的 store 解决方案是 pinia,用于替代 Vuex,我在开发过程中,部分场景会考虑使用 class 方式去管理数据,得益于 Vue3 响应式 API 可以脱离组件使用,只有业务型项目才会引入 pinia,这个仅我的观点,欢迎大家讨论;

  • 当一个独立的组件,功能比较复杂时,我会优先考虑 class 来进行数据层管理,class 也可以更好的提供 TS 类型推导,提供给外部使用;
  • 如果是一个业务系统类,我会考虑引入 pinia 进行数据管理,因为页面数据需要共享、数据持久化等;

class 写法

关于 reactiveref 的使用,我目前的用法是复杂数据类型我更愿意使用 reactive 来进行存储,因为在 Vue 模板之外不需要使用.value 语法,简易数据类型则使用 ref 进行存储,因为 reactive 不支持

拿我组件库 FormStore 写法举例,源码地址https://github.com/D-xuanmo/dl-ui/blob/develop/packages/dl-common/src/form/store/index.ts ,部分写法如下:

class Store {
  /**
   * 表单数据模型,使用 reactive 管理一个复杂的对象
   */
  private models: UnwrapNestedRefs<Map<string, IFormModelItem>> = reactive(new Map())

  /**
   * 表单禁用,使用 ref 管理一个独立的小数据
   */
  public formDisabled = ref(false)

  /**
   * 显示属性联动 store,对于 store 也需要进行功能拆分,各自管理自己的功能,在通过主 store 进行对外暴露
   */
  public viewLinkageStore = new ViewLinkageStore(this)

  /**
   * 明细表数据
   */
  public detailTableStore = new DetailTableStore(this)

  /**
   * 事件中心
   */
  public events = new EventEmitterEx()
}

封装组合式 API

因为一个大的组件,一般都有数据共享的场景,此时可以通过 provide 结合封装组合式 API 来提供方法暴露,比如我表单场景下的 events 事件中心需要共享,这个涉及到另一个概念,此处咱不做说明,有点偏题

events 一般在监听后,组件销毁时也需要注销,此时则可以提供一个组合式 API

import { useForm } from './use-form'
import { EventsType, IFormModelItem, IRenderModel } from '../types'
import { EventPrefixEnum } from '../constants'
import { onUnmounted } from 'vue'

/**
 * 定义一个用于触发事件的函数类型。
 * @param eventName - 事件名称
 * @param value - 事件的值
 * @param rowId - 行 ID(可选)
 * @typeparam T - 事件回调函数的参数类型,默认为 unknown
 */
type EmitType<T = unknown> = (eventName: string, value?: T, rowId?: string) => void

/**
 * 根据给定的模型对象注册事件监听器,并返回一个用于触发事件的函数。
 * @param model - 表单模型对象或渲染模型对象
 * @returns 一个函数,用于触发事件;如果发生异常,则返回 undefined
 * @template T - 事件回调函数的参数类型,默认为 unknown
 */
const emit = <T = unknown>(model: IFormModelItem | IRenderModel): EmitType<T> | undefined => {
  try {
    const { store } = useForm()
    const id = (model as IFormModelItem).dataKey || model.id

    /**
     * 触发事件的函数。
     * @param eventName - 事件名称
     * @param value - 事件的值
     * @param rowId - 行 ID(可选)
     */
    return (eventName, value, rowId) => {
      store.events.emit(`${EventPrefixEnum.FIELD}.${id}.${eventName}`, value, rowId)
    }
  } catch {
    return undefined
  }
}

/**
 * 注册事件监听器,并在组件卸载时自动取消注册。
 * @param eventName - 事件名称
 * @param callback - 事件回调函数
 */
const on = (eventName: EventsType, callback: (...params: any) => void) => {
  const { store } = useForm()

  // 注册事件的函数
  store.events.on(eventName, callback)

  // 在组件卸载时取消注册事件
  onUnmounted(() => {
    store.events.off(eventName, callback)
  })
}

/**
 * 定义一个 useFormEvent 函数,用于创建表单事件对象。
 * @param model - 表单模型对象或渲染模型对象
 * @returns 表单事件对象,包含 emit 和 on 方法
 * @template T - 事件回调函数的参数类型,默认为 unknown
 */
export const useFormEvent = <T = unknown>(model: IFormModelItem | IRenderModel) => {
  return {
    on,
    emit: emit<T>(model)
  }
}

pinia 大菠萝

pinia 则是全局类型的 store 共享数据,并非 class 可以做到单例,多实例共存,所以更适用于业务型项目,引用官网两段代码:

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0, name: 'Eduardo' }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
    },
  },
})

嵌套 store 的场景

import { useUserStore } from './user'

export const useCartStore = defineStore('cart', () => {
  const user = useUserStore()
  const list = ref([])

  const summary = computed(() => {
    return `Hi ${user.name}, you have ${list.value.length} items in your cart. It costs ${price.value}.`
  })

  function purchase() {
    return apiPurchase(user.id, this.list)
  }

  return { summary, purchase }
})

写在最后

两种 store 写法各有自己的业务场景,没有谁比谁好,选择自己合适方法进行开发则是最好的写法,欢迎大家讨论 [pp-玫瑰]

我的开源

  • 3人 Love
  • 0人 Haha
  • 0人 Wow
  • 0人 Sad
  • 0人 Angry
Vue3、Vue3 store

作者简介: 轩陌

打赏

生命的意义在于折腾,一直努力成长中,期待梦想实现的那刻。

共 0 条评论关于 “关于 Vue3 Store 的两种写法”

Loading...