前言
因为 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 写法
关于 reactive
和 ref
的使用,我目前的用法是复杂数据类型我更愿意使用 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 写法各有自己的业务场景,没有谁比谁好,选择自己合适方法进行开发则是最好的写法,欢迎大家讨论 。
我的开源
- Vue3 组件库(低代码方向):https://github.com/D-xuanmo/dl-ui
- 数据校验插件:https://github.com/D-xuanmo/validator