51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

关于 Vue3 Store 的两种写法

前言

因为 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-玫瑰]

我的开源

赞(0)
未经允许不得转载:工具盒子 » 关于 Vue3 Store 的两种写法