Pinia 原理解读 - 引入与初始化数据仓库流程
一、前置知识与入门
1.1 核心概念
Pinia 的特性:
-
pinia 也具有 state、getters、actions,但是移除了 modules、mutations ;
-
pinia 的 actions 里面可以支持同步也可以支持异步;
-
pinia 采用模块式管理,每个 store 都是独立的,互相不影响;
Pinia 与 Vuex 相比主要功能优点在于:
- 兼容支持 Vue 2.x 与 3.x 项目;
- 更友好的 Typescript 语法支持;
- 比 Vuex 体积更小,构建压缩后只有 1KB 左右的大小;
- 支持服务端渲染;
1.2 基本使用
Vue 应用注册引入 Pinia
// vue-project/src/main.ts
import { createSSRApp } from 'vue';
import * as store from 'pinia';
import App from './App.vue';
export function createApp() {
const app = createSSRApp(App);
app.use(store.createPinia());
return {
app,
store,
};
}
创建仓库模块
// /src/stores/user.ts
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({
id: '',
avatar: '',
nickname: '',
}),
actions: {
setId(id = '') {
this.id = id
},
},
});
仓库数据读取与设置
import { storeToRefs } from 'pinia';
import { useUserStore } from '@/stores/user';
const userStore = useUserStore();
const { id, info: userInfo } = storeToRefs(userStore);
// 批量修改数据
const patchStore = () => {
userStore.$patch({
avatar: 'xxxx',
nickname: "JesBrian",
});
};
// 调用 actions 方法
const setUserId = (id: string = '') => {
userStore.setId(id);
};
// 重置 store 数据为初始值
const reset = () => {
userStore.$reset();
};
二、源码原理的分析解读
2.1 createPinia
在上面基本使用的案例当中我们知道createPinia
方法是 Pinia 提供出来创建 Pinia 对象并且通过 vue.use 注册引入到 Vue 项目当中。
createPinia 概览
// vuejs:pinia/packages/pinia/src/createPinia.ts
export function createPinia(): Pinia {
const scope = effectScope(true)
const state = scope.run<Ref<Record<string, StateTree>>>(() =>
ref<Record<string, StateTree>>({})
)!
// ··· ···
}
在creatPinia
函数的最开始地方我们能看到,通过effectScope
声明了一个ref
的响应式数据,并赋值给了state
变量,这里的将其简单理解为声明了一个ref
并赋值给state
;
-
effectScope:这是一个 Vue 3.x 高阶的响应式的 api,能够对这个 effect 里面的响应式副作用(计算属性、监听器)统一进行操作处理,例如调用
stop
停止监听拦截等,具体可以查看文档:cn.vuejs.org/api/reactiv…
// vuejs:pinia/packages/pinia/src/createPinia.ts
export function createPinia(): Pinia {
// ··· ···
// 定义 pinia 插件相关的变量
let _p: Pinia["_p"] = [];
let toBeInstalled: PiniaPlugin[] = [];
// markRaw:标记该 pinia 不会被响应式转换和监听
const pinia: Pinia = markRaw({
install(app: App) { // install: vue.use实际执行逻辑
setActivePinia(pinia); // 设置当前使用数据仓库为根 pinia 对象
if (!isVue2) { // 如果是 vue 2.x,全局注册已经在 PiniaVuePlugin 完成,所以这段逻辑将跳过
pinia._a = app; // 设置 vue 项目的 app 实例
app.provide(piniaSymbol, pinia); // 通过 provide 来注入 pinia 实例
app.config.globalProperties.$pinia = pinia; // 在 vue 项目当中设置全局属性 $pinia
toBeInstalled.forEach((plugin) => _p.push(plugin)); // 处理安装未执行的 pinia 插件
toBeInstalled = []; // 清空未安装的插件列表
}
},
use(plugin) { // pinia 使用插件时候调用执行,将 pinia 插件都先塞到一个 _p 的数组当中,后续再进行初始化执行
if (!this._a && !isVue2) {
toBeInstalled.push(plugin);
} else {
_p.push(plugin);
}
return this;
},
_p, // pinia 的插件
_a: null, // Vue 项目的 app 实例,在 install 执行时设置
_e: scope, // pinia 的 effect 作用域对象,每个store都是单独的scope
_s: new Map<string, StoreGeneric>(), // 以 Map 的数据结构形式存储 pinia 数据仓库 store,类似 state
state, // pinia 数据仓库 state 合集
});
return pinia;
}
接着继续分析下面的逻辑能看到方法内通过markRaw
创建了一个包含 install、use、响应式数据 state 属性的 pinia 对象;并且最终将 pinia 对象作为createPinia
函数的返回值。
- 这个 pinia 对象经过
markRaw
的包装会被 Vue 标记为不会转化为响应式;能够节约内存的使用,提高运行效率。
Pinia 对象的属性
上面我们通过对createPinia
方法的分析已经了解到了 pinia 对象的创建的大概流程,这里我们简单分析描述下这个 pinia 对象上面的属性:
- install:方法是提供在 Vue 项目当中执行
app.use(pinia)
时候执行的;
-
- 调用执行时会保存当前的 pinia 对象,并且将该 pinia 对象注入到当前的 Vue 项目的 vue 实例的全局变量当中;
- use:是提供给 pinia 插件安装使用的方法,执行
pinia.use(devtoolsPlugin)
时候调用就是这个方法;
-
- 将插件注册到插件列表变量当中;
- _p:记录插件集合
- _a:当前的 vue 实例
- _e:store 数据仓库的 effect 作用域
- _s:以 Map 的数据结构形式存储 pinia 数据仓库 store
- state:指向 pinia 数据仓库 store 的数据
-
-
key为pinia的id value为store下的所有state(所有可访问变量)
-
与 Vue 2.x 版本的关联
Vue 2.x 使用 Pinia
查看 Pinia 的文档,Pinia 也是支持 Vue 2.x 版本的,但是在 Vue 2.x 环境当中需要在使用 createPinia 之前安装PiniaVuePlugin
插件。
import { createPinia, PiniaVuePlugin } from 'pinia'
Vue.use(PiniaVuePlugin) // 在 Vue 2.x 的项目当中需要额外执行,在 Vue 3.x 当中不需要
const pinia = createPinia()
new Vue({
el: '#app',
// 其他选项...
// ...
// 注意同一个 `pinia` 实例可以在多个 Vue 应用程序中使用
// 同一个页面
pinia,
})
PiniaVuePlugin 简析
// vuejs:pinia/packages/pinia/src/vue2-plugin.ts
export const PiniaVuePlugin: Plugin = function (_Vue) {
_Vue.mixin({
beforeCreate() {
const options = this.$options
if (options.pinia) {
const pinia = options.pinia as Pinia
if (!(this as any)._provided) {
const provideCache = {}
Object.defineProperty(this, '_provided', {
get: () => provideCache,
set: (v) => Object.assign(provideCache, v),
})
}
;(this as any)._provided[piniaSymbol as any] = pinia
if (!this.$pinia) {
this.$pinia = pinia
}
pinia._a = this as any
if (IS_CLIENT) {
setActivePinia(pinia)
}
} else if (!this.$pinia && options.parent && options.parent.$pinia) {
this.$pinia = options.parent.$pinia
}
},
destroyed() {
delete this._pStores
},
})
}
通过阅读上述 PiniaVuePlugin 的源码,如果了解过 Vuex 的实现原理的话能够发现两者是类似的实现原理,通过 Vue 框架提供的 mixin 的能力来对 this 对象的 $pinia 属性注入,从而实现全局数据仓库的的一个共享访问。
-
在 beforeCreate 创建组件实例前阶段进行 $pinia 属性的挂载;
-
在 destoryed 销毁组件实例阶段对当前组件实例卸载删除数据连接;
小结:
至此 Pinia 的注册流程已经解析完了,在这个章节当中我们主要对 Vue 项目当中引入 Pinia 的流程和原理进行了分析,接下来我们会对 Pinia 初始化创建数据仓库这个阶段进行源码解析。
2.2 defineStore
数据仓库的初始化
defineStore
该方法在上面 “基础使用” 的章节当中我们能够看到是用来创建 Pinia 数据仓库模块。接下来这个章节我们便要来对该方法进行原理性的解剖分析。
三种参数形式的初始化调用
import { ref } from 'vue';
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({
name: '',
age: 0,
}),
getters: {
description() {
return `${this.name} - ${this.age}`;
},
},
actions: {
setName(name = '') {
return this.name = name;
},
setAge(age = 10) {
return this.age = age;
},
},
});
export const useAdminStore = defineStore({
id: 'admin',
state: () => ({
name: '',
level: 0,
}),
getters: {
description(): string {
return `${this.name} - ${this.level}`;
},
},
actions: {
setName(name = '') {
return this.name = name;
},
setAge(age = 10) {
return this.age = age;
},
},
});
export const useAuthStore = defineStore('auth', () => {
const bit = ref(0);
const checkPermission = () => {
// ··· ···
}
return {
bit, checkPermission
}
});
在上述例子当中能够看到defineStore
有传入三种的形式的参数的调用方式:
- id:定义 store 的唯一 id,单独传参或者通过 options.id 进行声明
- options:具体配置信息,是对象可以传 state,getters,action,id 属性例如上面例子中的第一、二种声明方式;如果第二个参数传入的是 Function,则自主声明变量方法,例如例子中的第三种声明方式;
- storeSetup:仅限第三种 store 的声明方式,传入一个函数,函数的返回值作为数据仓库的 state 与 actions
查看defineStore
具体的源码发现是利用 TypeScript 函数重载来实现传递不同参数进行数据仓库的初始化处理。
- 上述的例子当中对应下面源码当中 TS 定义的三种 defineStore 重载形式
// vuejs:pinia/packages/pinia/src/store.ts
export function defineStore<
Id extends string,
S extends StateTree = {},
G extends _GettersTree<S> = {},
// cannot extends ActionsTree because we loose the typings
A /* extends ActionsTree */ = {}
>(
id: Id,
options: Omit<DefineStoreOptions<Id, S, G, A>, 'id'>
): StoreDefinition<Id, S, G, A>
export function defineStore<
Id extends string,
S extends StateTree = {},
G extends _GettersTree<S> = {},
// cannot extends ActionsTree because we loose the typings
A /* extends ActionsTree */ = {}
>(options: DefineStoreOptions<Id, S, G, A>): StoreDefinition<Id, S, G, A>
export function defineStore<Id extends string, SS>(
id: Id,
storeSetup: () => SS,
options?: DefineSetupStoreOptions<
Id,
_ExtractStateFromSetupStore<SS>,
_ExtractGettersFromSetupStore<SS>,
_ExtractActionsFromSetupStore<SS>
>
): StoreDefinition<
Id,
_ExtractStateFromSetupStore<SS>,
_ExtractGettersFromSetupStore<SS>,
_ExtractActionsFromSetupStore<SS>
>
初始化仓库具体逻辑
// vuejs:pinia/packages/pinia/src/store.ts
export function defineStore(
idOrOptions: any,
setup?: any,
setupOptions?: any
): StoreDefinition {
let id: string
let options:
| DefineStoreOptions<
string,
StateTree,
_GettersTree<StateTree>,
_ActionsTree
>
| DefineSetupStoreOptions<
string,
StateTree,
_GettersTree<StateTree>,
_ActionsTree
>
// 判断第二个参数是否是函数
const isSetupStore = typeof setup === 'function'
// 兼容处理三种不同参数重载的情况,从三种形式参数当中抽取出 id 与 options 参数;
// id 后续会作为当前该 store 的模块标识 id
if (typeof idOrOptions === 'string') {
id = idOrOptions
options = isSetupStore ? setupOptions : setup
} else {
options = idOrOptions
id = idOrOptions.id
}
// 声明 useStore 函数并且作为 defineStore 函数的返回值
function useStore(pinia?: Pinia | null, hot?: StoreGeneric): StoreGeneric {
// ··· ···
}
useStore.$id = id
return useStore
}
通过对defineStore
的源码大致逻辑流程分析可以得知,defineStore
里面含有一个useStore
方法,并且defineStore
函数的返回值为该useStore
函数。因此useStore
才是 Pinia store 数据仓库的核心创建逻辑,接下我们重点分析其函数逻辑。
useStore 解析
// vuejs:pinia/packages/pinia/src/store.ts
export function defineStore(
idOrOptions: any,
setup?: any,
setupOptions?: any
): StoreDefinition {
// ··· ···
function useStore(pinia?: Pinia | null, hot?: StoreGeneric): StoreGeneric {
// 通过 getCurrentInstance 获取当前 Vue 的组件实例
const currentInstance = getCurrentInstance()
//
pinia =
(__TEST__ && activePinia && activePinia._testing ? null : pinia) ||
(currentInstance && inject(piniaSymbol, null))
// 设置当前 Pinia 活跃的 state 实例
if (pinia) setActivePinia(pinia)
// 开发模式下并且当前没有 pinia 仓库数据 state 实例情况下抛出异常
if (__DEV__ && !activePinia) {
throw new Error(
`[🍍]: getActivePinia was called with no active Pinia. Did you forget to install pinia?\n` +
`\tconst pinia = createPinia()\n` +
`\tapp.use(pinia)\n` +
`This will fail in production.`
)
}
// 设置当前 pinia 变量值为当前活跃的 pinia state 实例
pinia = activePinia!
// 单例模式的应用:如果 pinia 中已经有对应 id 模块的 store 实例则直接获取该 store 实例返回,否则执行创建 store 逻辑
if (!pinia._s.has(id)) {
// 根据 defineStore 时候传入第二个参数类型区分调用创建 pinia store 数据仓库方法
// 若第二个参数传递的是函数则执行 createSetupStore,否则执行 createOptionsStore
if (isSetupStore) {
createSetupStore(id, setup, options, pinia)
} else {
createOptionsStore(id, options as any, pinia)
}
}
const store: StoreGeneric = pinia._s.get(id)!
// StoreGeneric cannot be casted towards Store
return store as any
}
useStore.$id = id
return useStore
}
useStore
的逻辑其实非常简单,就是获取当前 id 的 pinia store 实例,这里使用单例模式进行优化:
- 如果 pinia 中已经有对应 id 模块的 store 实例则直接获取该 store 实例返回;
-
- 前面在
createStore
方法章节当中解析了当前 pinia 对象的 _s 属性是 Map 的数据结构对 Pinia 所有模块 store 的存储,因此此处使用Map
的get(key)
方法获取当前 id 对应的模块 store 实例;
- 前面在
-
否则根据前面
defineStore
调用时候传递的参数进行区分调用createOptionsStore
与createSetupStore
两个方法。
createOptionsStore
我们先来看createOptionsStore
这个比较简单的方法,这个方法是调用defineStore
函数时候传入 Object 对象数据结构参数的形式才会执行。
// vuejs:pinia/packages/pinia/src/store.ts
function createOptionsStore<
Id extends string,
S extends StateTree,
G extends _GettersTree<S>,
A extends _ActionsTree
>(
id: Id,
options: DefineStoreOptions<Id, S, G, A>,
pinia: Pinia,
hot?: boolean
): Store<Id, S, G, A> {
const { state, actions, getters } = options
// 判断能否从当前 pinia 已经实例化的 store 数据仓库集 state 当中获取当前 ID 的数据仓库
const initialState: StateTree | undefined = pinia.state.value[id]
let store: Store<Id, S, G, A>
function setup() {
// 如果没有初始化过当前 ID 的 state 则使用 options 的 state 方法创建一个响应式的数据
if (!initialState) {
pinia.state.value[id] = state ? state() : {}
}
// 通过 toRefs 获取一个解构仍能保持响应式的当前 ID 的 state 数据仓库
const localState = toRefs(pinia.state.value[id])
return assign(
localState,
actions,
Object.keys(getters || {}).reduce((computedGetters, name) => {
// 判断如果 getter 的 key 已经在 state 当中则进行 warn 提示处理
if (__DEV__ && name in localState) {
console.warn(
`[🍍]: A getter cannot have the same name as another state property. Rename one of them. Found with "${name}" in store "${id}".`
)
}
// 使用 markRaw 标记对象,防止对象被 Proxy 劫持成为响应式数据
computedGetters[name] = markRaw(
// 使用计算属性处理 options 的 getters -- 也是因为这步操作使得 pinia 的 getters 拥有 Vue.js 的 computed 的能力
computed(() => {
setActivePinia(pinia)
const store = pinia._s.get(id)!
// 通过 call 处理 this 指向
return getters![name].call(store, store)
})
)
return computedGetters
}, {} as Record<string, ComputedRef>)
)
}
// 创建和整理好各个参数后调用 createSetupStore 方法
store = createSetupStore(id, setup, options, pinia, hot, true)
// 给 pinia store 设置 $reset 方法
store.$reset = function $reset() {
const newState = state ? state() : {}
this.$patch(($state) => {
assign($state, newState)
})
}
return store as any
}
可以看到其实createOptionStore
方法内部最主要还是根据 options 对象里面的数据,在方法内部构建并且封装为setup
函数,setup 函数当中主要是将 options 参数中的state
与getters
属性分别使用toRefs
和computed
封装转化为 Vue.js 的ref
响应式数据与computed
计算属性并返回;
- state 会先判断是否已经有实例化响应式的 state 数据,若没有则调用 options 的 state 方法进行响应式数据实例;
- 接着将
state
与getters
属性分别使用toRefs
和computed
封装转化为 Vue.js 的ref
响应式数据与computed
计算属性作为 setup 函数的返回值对象属性处理;
-
- 也是因为这步操作使得 pinia 数据仓库的 state 的里面的属性具有响应式能力及 getters 具有计算属性的能力;
- options 中的 actions 属性保持原样作为 setup 函数返回的对象属性,后续会在
createSetupStore
内进行进一步处理;
-
- 这里留意
createSetupStore
方法有一个isOptionsStore
参数,标记是否是以 options 的形式进行创建数据模块,这里通过createOptionStore
调用的createSetupStore
则会传递 true 值。
- 这里留意
创建好setup
函数后接着便是调用createSetupStore
传入 id、刚封装的 setup 方法等参数,因此其实无论以哪种参数形式调用defineStore
方法最终走的都是createSetupStore
方法对 pinia store 进行创建初始化处理。
createSetupStore
接着我们继续看createSetupStore
这方法,从前面的分析可以看到其实那三种调用deineStore
定义 pinia store 的不同参数方法最终都是归并参数成setup
方法然后调用createSetupStore
方法进行创建数据仓库。
// vuejs:pinia/packages/pinia/src/store.ts
function createSetupStore<
Id extends string,
SS extends Record<any, unknown>,
S extends StateTree,
G extends Record<string, _Method>,
A extends _ActionsTree
>(
$id: Id,
setup: () => SS,
options:
| DefineSetupStoreOptions<Id, S, G, A>
| DefineStoreOptions<Id, S, G, A> = {},
pinia: Pinia,
hot?: boolean,
isOptionsStore?: boolean
): Store<Id, S, G, A> {
let scope!: EffectScope
const setupStore = pinia._e.run(() => {
scope = effectScope()
return scope.run(() => setup())
})!
// ··· ···
}
可以看到createSetupStore
方法当中会将前面统一封装处理的setup
方法包裹在一个effectScope
当中运行,结合着前面definedStore
三种形式的传参以及createOptionStore
对setup
方法的定义封装,我们能够知道setup
函数运行后能返回的是经过处理的响应式的 state、基于 computed 计算属性实现的 getters 以及对应的 action 方法的一个对象seupStore
。
对 store 的 action 方法处理增加监听后还需要对当前状态库模块 store 进行一些操作 api 的挂载,例如 reset,reset,reset,patch 等方法。下面的源码片段当中能看到这里定义了$patch
、$reset
、$dispose
、$subscribe
、$onAction
等相关方法,然后使用Object.assign
进行两个对象之间的属合并,从而将相关的 api 进行挂载到当前模块的 store 上。
- 不过因为篇幅的原因,这里暂时先不展开分析里面各个 api 的具体逻辑实现了,后续的系列文章会继续重拾这块进行详细的分析。
// vuejs:pinia/packages/pinia/src/store.ts
const { assign } = Object
function createSetupStore<
Id extends string,
SS extends Record<any, unknown>,
S extends StateTree,
G extends Record<string, _Method>,
A extends _ActionsTree
>(
$id: Id,
setup: () => SS,
options:
| DefineSetupStoreOptions<Id, S, G, A>
| DefineStoreOptions<Id, S, G, A> = {},
pinia: Pinia,
hot?: boolean,
isOptionsStore?: boolean
): Store<Id, S, G, A> {
let scope!: EffectScope
function $patch(stateMutation: (state: UnwrapRef<S>) => void): void
function $patch(partialState: _DeepPartial<UnwrapRef<S>>): void
function $patch(
partialStateOrMutator:
| _DeepPartial<UnwrapRef<S>>
| ((state: UnwrapRef<S>) => void)
): void {
// ··· ···
}
const $reset = __DEV__ ? () => {
// ··· ···
} : noop
function $dispose() {
// ······
}
const partialStore = {
_p: pinia,
// _s: scope,
$id,
$onAction: //··· ···,
$patch,
$reset,
$subscribe(callback, options = {}) {
// ··· ···
},
$dispose,
} as _StoreWithState<Id, S, G, A>
const store: Store<Id, S, G, A> = reactive(partialStore) as unknown as Store<Id, S, G, A>
// TODO: idea create skipSerialize that marks properties as non serializable and they are skipped
const setupStore = pinia._e.run(() => {
scope = effectScope()
return scope.run(() => setup())
})!
assign(store, setupStore)
return store
}
在对 store 添加完 api 后便是初始化 pinia 的插件了,在前面createPinia
的章节当中,我们分析了 pinia 的实例对象当中存在一个_p
属性,里面是 pinia 的插件合集,在defineStore
-createSetupStore
的时候会将当前的 store 传递给 pinia 的插件列表,一个个插件项进行直接调用实现插件的初始化处理。
// vuejs:pinia/packages/pinia/src/store.ts
function createSetupStore<
Id extends string,
SS extends Record<any, unknown>,
S extends StateTree,
G extends Record<string, _Method>,
A extends _ActionsTree
>(
$id: Id,
setup: () => SS,
options:
| DefineSetupStoreOptions<Id, S, G, A>
| DefineStoreOptions<Id, S, G, A> = {},
pinia: Pinia,
hot?: boolean,
isOptionsStore?: boolean
): Store<Id, S, G, A> {
// ··· ···
const optionsForPlugin: DefineStoreOptionsInPlugin<Id, S, G, A> = assign(
{ actions: {} as A },
options
)
// 遍历 pinia 插件列表
pinia._p.forEach((extender) => {
assign(
store,
scope.run(() =>
// 调用 pinia 插件的 extender 方法进行安装
extender({
store,
app: pinia._a,
pinia,
options: optionsForPlugin,
})
)!
)
})
return store
}
处理完 pinia 的插件在当前 store 数据仓库当中的初始化逻辑后,createSetupStore
方法也基本走完全部流程了,后面就是将这个 store 对象作为createSetupStore
方法的返回值,也就是执行了defineStore
函数后能够获取到的返回值即为该模块的数据仓库 store 对象。
小结:
通过本章节对defineStore
这个创建 Pinia state 数据仓库的方法进行源码阅读与分析,我们了解到了 Pinia 是如何通过defineStore
方法进行创建和初始化一个 store 数据仓库模块的,并且了解学习了单例模式在源码实例化当中的优化使用的例子;以及一个 store 数据仓库里面的 state、getters、action 这三个属性里面是分别如何实现的,并且对初始化创建的模块 store 进行 api 的挂载处理以及 pinia 插件的初始化加载。
三、初始化流程总结
至此,我们基本对 Pinia 数据仓库的初始化流程了解和学习了一遍,这里简单的进行一个总结:
-
通过
createPinia
创建 pinia 实例,并且在app.use
时候执行 pinia 内部的install
方法;install
方法当中会使用 Vue.js 的provide
将当前 pinia 实例注入到每一个 Vue 组件实例当中;
-
在业务当中使用
useXxx
时候其实调用的是defineStore
方法,该defineStore
方法只有在真正调用时候才会初始化创建对应模块的数据仓库;defineStore
方法的内部处理逻辑:- 创建一个 store 对象,将 options 的各个属性 state、getters 利用 Vue.js 的响应式 Composition API 进行处理和转换,使之成为响应式的数据后挂载在这个 store 对象上;
- a
- 通过
Object.assign
方法对这个 store 对象进行一些 api (onAction、onAction、onAction、reset、$patch 等)的扩展挂载; - 将 pinia 的插件在这个模块化的 store 上的初始化加载;
- 返回这个 store 对象作为
defineStore
方法的返回值;
-
至此,pinia 这个全局状态管理工具的引入和模块化数据仓库 store 的初始化流程就完成了。
Pinia 初始化的流程大致分析至此,Pinia 这个库肯定不仅仅是这些内容,像是发布订阅触发,store上面的各种操作方法还没进行源码实现分析,后续也会不断深入探究这块的内容,并且持续的输出相关的原理解析、理解心得的文章。
参考资料
- Pinia 🍍:pinia.web3doc.top
- 响应式 API:进阶 | Vue.js:cn.vuejs.org/api/reactiv…
- Vue源码解析系列(十六) -- vuex、pinia实现的状态管理原理与源码解读:juejin.cn/post/714317…
转载自:https://juejin.cn/post/7210175991837736997