众所周知,相信大家用过 Pinia 的都知道,Pinia 定义储存采用的是 Centralized State Management(集中式状态管理)的模式,从而区分 State(状态)、Actions(操作)、Getters(计算),而这种模式非常利于数据与视图的逻辑分离:
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() {
this.count++
},
},
getters: {
doubled() {
return this.count * 2
},
}
})
而视图组件则只需要 use 对应的数据集合,则可直接读取统一管理的数据:
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
counter.count++
counter.increment()
</script>
<template>
<div>Current Count: {{ counter.count }}</div>
</template>
Pinia 凭借简洁的语法定义与逻辑架构,完美解决了 Vuex 遗留的类型推导缺失及魔法字符串问题。目前 Pinia 已成为 Vue 生态中数据管理的首选方案,足以证明了该设计模式对开发者体验的巨大提升。
React
React 的状态管理由社区支持,状态库层出不穷,从很久以前的 Redux、Mobx,到现在流行的 Zustand、Valtio、Recoil、Jotai、XState,每一个都有自己的设计哲学,作为一个前端开发,大部分我都用了个遍,说实话你问我哪个语法上好用,我是真不知道,它们每一个都可以作为生产级别的数据储存,但我都觉得它们要么太复杂、抽象度太高;要么语法是简洁了,但 State、Action 非常零散,接手项目的时候看的人脑壳痛。
大伙可能想到了,和 Pinia 类似的不就是 Valtio。作为一个轻量级的状态管理库,Valtio 在数据管理的逻辑上,使用 proxy(代理)驱动视图更新,与 Pinia 和 vue3 的逻辑非常相似,这样的状态管理方式更加精简,学习曲线也更低,也不需要 dispatch 之类的概念。
但 Valtio 定义数据的方式则是直接定义代理对象,作为快速启动中小型项目来说是很方便,但是一但数据多起来了,每个页面的 actions 跟 getters 就会非常分散。而我们只需要解决这个问题就可以了。
Valtio Define
首先是 API 的设计,我们就参考 Pinia 的模式,通过包装器(defineStore)作为定义数据的手段:
import { defineStore } from 'valtio-define'
const store = defineStore({
state: () => ({ count: 0 }),
actions: {
increment() {
this.count++
},
},
getters: {
doubled() {
return this.count * 2
},
}
})
store.$state // { count: number }
store.$getters // { doubled: number }
store.$subscribe // Function -> 等同于 subscribe(store.$state)
store.increment // Function
使用钩子(useStore),作为视图引用的方法:
function Counter() {
const { count, doubled } = useStore(store)
return (
<div>
<button onClick={store.increment}>Increment</button>
<div>{count} / x2:{doubled}</div>
</div>
)
}
这里我们与 Pinia 会有一些差异,由于
valtio定义与使用是分离的(这其实也是 react 框架的差异),我们则遵从框架的设计理念,将 hook 分离了出来。
插件
当然少不了插件的设计,这里就拿作为使用率最高的 persist(持久化储存)来说,我们在定义插件时也要保证精简的原则:
import type { Plugin } from 'valtio-define'
import type { PersistentOptions } from 'valtio-define'
import { subscribe } from 'valtio'
export function persist(): Plugin {
return ({ store, options }) => {
...
function watch() {
subscribe(store.$state, () => {
...
})
}
watch()
}
}
declare module 'valtio-define' {
export interface StoreDefineOptions<S extends object> {
persist?: PersistentOptions<S> | boolean
}
}
那么在使用的时候,我们得可以通过 store.use,去加载这个插件:
const store = defineStore({
state: () => ({ count: 0 }),
actions: {
increment() {
this.count++
},
},
// 通过选项或配置开启持久化
persist: true,
})
store.use(persist())
当然,为了节省时间,也支持从全局中加载:
import valtio from 'valtio-define'
// Register the persist plugin globally
valtio.use(persist())
省流
当然啦,我怎么会忍心让你们写代码,如果你喜欢这样的方式,我已经把上面的所有功能都做成了一个项目了(valtio-define)
觉得不错的点个 Star!这对我真的很有帮助,谢谢啦。