复习vuex
vuex使用方法
store/index.js
import {createStore} from 'vuex'
const store = createStore({
state(){
return {
cnt:111
}
},
mutations: {
addCnt(state){
state.cnt++
}
}
})
export default store
test.vue
<template>
<div>
<button @click="test">{{store.state.cnt}}</button>
</div>
</template>
<script setup lang="ts">
import {useStore} from 'vuex'
const store = useStore()
function test(){
store.commit("addCnt")
}
</script>
<style scoped>
</style>
vuex源码实现
vuexMini.js
import {inject,provide,reactive,computed} from 'vue'
const storeKey = '__store__key__'
function useStore(){
return inject(storeKey)//这里返回注入的 store
}
function createStore(options){
return new Store(options)
}
class Store{
constructor(options){
this._state = reactive({
data:options.state() //这里需要主动调用一次state才能返回数据
})
this._mutations = options.mutations
this._actions = options.actions
this.getters = {}
const kes = Object.keys(options.getters)
for (let index = 0; index < kes.length; index++) {
const key = kes[index];
const fun = options.getters[key]
Object.defineProperty(this.getters,key,{
get: ()=>{
return computed(()=>{ //设置缓存
return fun(this)
}
)
}
})
}
}
install(app) {//注册时候的代码,把store挂载到全局
app.provide(storeKey,this)
}
get state(){
return this._state.data // 这里只返回内部变量的data
}
commit(key,palyload){
this._mutations[key](this,palyload)
}
dispatch(key,palyload){
this._actions[key](this,palyload)
}
}
export {createStore, useStore}
store/index.js
import {createStore} from '../vuexMini.js'
const store = createStore({
state(){
return {
cnt:111
}
},
mutations: {
addCnt(store){
store.state.cnt++
}
},
getters: {
doubleCnt(store){
return store.state.cnt * 2
}
},
actions: {
addCntByReq( store){
new Promise( (resolve,reject)=> {
setTimeout(() => {
resolve()
}, 1000);
}).then((res) => {
store.commit("addCnt",100)
})
}
}
})
export default store
test.vue
<template>
<div>
{{store.state.cnt}}
{{store.getters.doubleCnt}}
<button @click="test">xxx</button>
</div>
</template>
<script setup lang="ts">
import {useStore} from '../vuexMini.js'
const store = useStore()
function test(){
store.commit("addCnt",store.state.cnt++)
store.dispatch('addCntByReq')
}
</script>
<style scoped>
</style>
pinia
优势
- 同时兼容vue2 和 vue3
- 支持option 和 composition的写法
- 去掉了vuex中mutation的概念,不区分同步异步方法,而是在action里面直接编写async/await
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: null,
loading: false,
}),
actions: {
async fetchUserInfo() {
this.loading = true; // 更新状态
try {
const response = await fetch('/api/user'); // 异步请求
this.userInfo = await response.json(); // 设置用户信息
} catch (error) {
console.error("Failed to fetch user info:", error);
} finally {
this.loading = false; // 完成后重置 loading 状态
}
}
}
});
使用方法
npm install pinia
// main.js
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
const app = createApp(App);
const pinia = createPinia();
app.use(pinia);
app.mount('#app');
option写法
// stores/count.js
import { defineStore } from 'pinia';
export const useCountStore = defineStore('count', {
state: () => ({
count: 0, // 初始化计数器
}),
actions: {
increment() {
this.count++;
},
decrement() {
this.count--;
},
async incrementAfterDelay() {
// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, 1000));
this.count++;
}
},
});
<template>
<div>
<h1>Count: {{ countStore.count }}</h1>
<button @click="increment">Increment</button>
<button @click="decrement">Decrement</button>
<button @click="incrementAfterDelay">Increment After Delay</button>
</div>
</template>
<script setup>
import { useCountStore } from '@/stores/count';
// 初始化 store
const countStore = useCountStore();
const { increment, decrement, incrementAfterDelay } = countStore;
</script>
compositonApi写法
// stores/count.js
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useCountStore = defineStore('count', () => {
// 使用 ref 创建响应式状态
const count = ref(0);
// 同步增减方法
const increment = () => {
count.value++;
};
const decrement = () => {
count.value--;
};
// 异步增加方法
const incrementAfterDelay = async () => {
await new Promise(resolve => setTimeout(resolve, 1000)); // 模拟延迟
count.value++;
};
return {
count,
increment,
decrement,
incrementAfterDelay,
};
});
源码部署
版本 2.2.6
git clone https://github.com/vuejs/pinia.git
pnpm i #安装依赖
pnpm play # 运行
服务器启动后:
点击demo-counter 例子
源码例子
我们打开代码看到这个定义和使用的文件
定义
packages/playground/src/stores/demo-counter.ts
export const useCounter = defineStore('demo-counter', {
state: () => ({
n: 0,
}),
getters: {
double: (state) => state.n * 2,
},
})
packages/playground/src/views/DemoCounter.vue
<template>
<h2>Counter Store</h2>
<p>Counter :{{ counter.n }}. Double: {{ counter.double }}</p>
<button @click="counter.n++">Increment</button>
<hr />
<p>
<code>counter.$state</code>:
</p>
<pre>{{ counter.$state }}</pre>
</template>
<script lang="ts" setup>
import { useCounter } from '../stores/demo-counter'
const counter = useCounter()
</script>
- 通过useCounter获得对象
- 通过counter.$state访问属性
- 通过double获取计算属性
调试
我们在main.js加上debugger
执行了createPinia代码
- 使用effectScope创建数据管理对象
- 使用scope.run里面定义一个 ref({})对象作为state
- 返回一个pinia实例,包含use 和 install方法,还注册了provide 对应实例的全局对象
- 当 createApp(App).use(pinia) 时候触发install 逻辑
createPinia.ts
createPinia
import { Pinia, PiniaPlugin, setActivePinia, piniaSymbol } from './rootStore'
import { ref, App, markRaw, effectScope, isVue2, Ref } from 'vue-demi'
import { registerPiniaDevtools, devtoolsPlugin } from './devtools'
import { IS_CLIENT } from './env'
import { StateTree, StoreGeneric } from './types'
/**
* Creates a Pinia instance to be used by the application
*/
export function createPinia(): Pinia {
const scope = effectScope(true)
// NOTE: here we could check the window object for a state and directly set it
// if there is anything like it with Vue 3 SSR
const state = scope.run<Ref<Record<string, StateTree>>>(() =>
ref<Record<string, StateTree>>({})
)!
let _p: Pinia['_p'] = []
// plugins added before calling app.use(pinia)
let toBeInstalled: PiniaPlugin[] = []
const pinia: Pinia = markRaw({
install(app: App) {
// this allows calling useStore() outside of a component setup after
// installing pinia's plugin
setActivePinia(pinia)
if (!isVue2) {
pinia._a = app
app.provide(piniaSymbol, pinia)
app.config.globalProperties.$pinia = pinia
/* istanbul ignore else */
if (__USE_DEVTOOLS__ && IS_CLIENT) {
registerPiniaDevtools(app, pinia)
}
toBeInstalled.forEach((plugin) => _p.push(plugin))
toBeInstalled = []
}
},
use(plugin) {
if (!this._a && !isVue2) {
toBeInstalled.push(plugin)
} else {
_p.push(plugin)
}
return this
},
_p,
// it's actually undefined here
// @ts-expect-error
_a: null,
_e: scope,
_s: new Map<string, StoreGeneric>(),
state,
})
// pinia devtools rely on dev only features so they cannot be forced unless
// the dev build of Vue is used. Avoid old browsers like IE11.
if (__USE_DEVTOOLS__ && typeof Proxy !== 'undefined') {
pinia.use(devtoolsPlugin)
}
return pinia
}
/**
* Dispose a Pinia instance by stopping its effectScope and removing the state, plugins and stores. This is mostly
* useful in tests, with both a testing pinia or a regular pinia and in applications that use multiple pinia instances.
* Once disposed, the pinia instance cannot be used anymore.
*
* @param pinia - pinia instance
*/
export function disposePinia(pinia: Pinia) {
pinia._e.stop()
pinia._s.clear()
pinia._p.splice(0)
pinia.state.value = {}
// @ts-expect-error: non valid
pinia._a = null
}
store.ts
packages/pinia/src/store.ts
defineStore
支持3种方式声明,主要就是 option 或 setup
export function defineStore(
// TODO: add proper types from above
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'
if (typeof idOrOptions === 'string') {
id = idOrOptions
// the option store setup will contain the actual options in this case
options = isSetupStore ? setupOptions : setup
} else {
options = idOrOptions
id = idOrOptions.id
}
function useStore(pinia?: Pinia | null, hot?: StoreGeneric): StoreGeneric {
const hasContext = hasInjectionContext()
pinia =
if (pinia) setActivePinia(pinia)
pinia = activePinia!
if (!pinia._s.has(id)) { // 这里通过_s缓存数据集合
// creating the store registers it in `pinia._s`
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
}
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>
/**
* Creates a `useStore` function that retrieves the store instance
*
* @param options - options to define the store
*/
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>
/**
* Creates a `useStore` function that retrieves the store instance
*
* @param id - id of the store (must be unique)
* @param storeSetup - function that defines the store
* @param options - extra options
*/
export function defineStore<Id extends string, SS>(
id: Id,
storeSetup: (helpers: SetupStoreHelpers) => SS,
options?: DefineSetupStoreOptions<
Id,
_ExtractStateFromSetupStore<SS>,
_ExtractGettersFromSetupStore<SS>,
_ExtractActionsFromSetupStore<SS>
>
): StoreDefinition<
Id,
_ExtractStateFromSetupStore<SS>,
_ExtractGettersFromSetupStore<SS>,
_ExtractActionsFromSetupStore<SS>
>
createSetupStore
createSetupStore方式创建store
- $patch实现实时更新,参数partialStateOrMutator 是函数时候 会立即执行
- mergeReactiveObjects方法用来合并对象数据,合并后保存在subscriptionMutation
- 通过triggerSubscriptions方法触发
- 创建一个 partialStore 对象 用于记录相关id $patch、Pinia实例 subscribe订阅的方法
- 创建一个store = reactive(partialStore) 包裹
- 最后返回store
- const store = pinia._s.get(id)!可以获取对应的partialStore
function createSetupStore<
Id extends string,
SS extends Record<any, unknown>,
S extends StateTree,
G extends Record<string, _Method>,
A extends _ActionsTree,
>(
$id: Id,
setup: (helpers: SetupStoreHelpers) => 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 optionsForPlugin: DefineStoreOptionsInPlugin<Id, S, G, A> = assign(
{ actions: {} as A },
options
)
// watcher options for $subscribe
const $subscribeOptions: WatchOptions = { deep: true }
/* istanbul ignore else */
// internal state
let isListening: boolean // set to true at the end
let isSyncListening: boolean // set to true at the end
let subscriptions: SubscriptionCallback<S>[] = []
let actionSubscriptions: StoreOnActionListener<Id, S, G, A>[] = []
let debuggerEvents: DebuggerEvent[] | DebuggerEvent
const initialState = pinia.state.value[$id] as UnwrapRef<S> | undefined
// avoid setting the state for option stores if it is set
// by the setup
if (!isOptionsStore && !initialState && (!__DEV__ || !hot)) {
/* istanbul ignore if */
if (isVue2) {
set(pinia.state.value, $id, {})
} else {
pinia.state.value[$id] = {}
}
}
const hotState = ref({} as S)
// avoid triggering too many listeners
// https://github.com/vuejs/pinia/issues/1129
let activeListener: Symbol | undefined
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 {
let subscriptionMutation: SubscriptionCallbackMutation<S>
isListening = isSyncListening = false
// reset the debugger events since patches are sync
/* istanbul ignore else */
if (typeof partialStateOrMutator === 'function') {
partialStateOrMutator(pinia.state.value[$id] as UnwrapRef<S>)
subscriptionMutation = {
type: MutationType.patchFunction,
storeId: $id,
events: debuggerEvents as DebuggerEvent[],
}
} else {
mergeReactiveObjects(pinia.state.value[$id], partialStateOrMutator)
subscriptionMutation = {
type: MutationType.patchObject,
payload: partialStateOrMutator,
storeId: $id,
events: debuggerEvents as DebuggerEvent[],
}
}
const myListenerId = (activeListener = Symbol())
nextTick().then(() => {
if (activeListener === myListenerId) {
isListening = true
}
})
isSyncListening = true
// because we paused the watcher, we need to manually call the subscriptions
triggerSubscriptions(
subscriptions,
subscriptionMutation,
pinia.state.value[$id] as UnwrapRef<S>
)
}
const $reset = isOptionsStore
? function $reset(this: _StoreWithState<Id, S, G, A>) {
const { state } = options as DefineStoreOptions<Id, S, G, A>
const newState: _DeepPartial<UnwrapRef<S>> = state ? state() : {}
// we use a patch to group all changes into one single subscription
this.$patch(($state) => {
// @ts-expect-error: FIXME: shouldn't error?
assign($state, newState)
})
}
function $dispose() {
scope.stop()
subscriptions = []
actionSubscriptions = []
pinia._s.delete($id)
}
/**
* Helper that wraps function so it can be tracked with $onAction
* @param fn - action to wrap
* @param name - name of the action
*/
const action = <Fn extends _Method>(fn: Fn, name: string = ''): Fn => {
if (ACTION_MARKER in fn) {
// we ensure the name is set from the returned function
;(fn as unknown as MarkedAction<Fn>)[ACTION_NAME] = name
return fn
}
const wrappedAction = function (this: any) {
setActivePinia(pinia)
const args = Array.from(arguments)
const afterCallbackList: Array<(resolvedReturn: any) => any> = []
const onErrorCallbackList: Array<(error: unknown) => unknown> = []
function after(callback: _ArrayType<typeof afterCallbackList>) {
afterCallbackList.push(callback)
}
function onError(callback: _ArrayType<typeof onErrorCallbackList>) {
onErrorCallbackList.push(callback)
}
// @ts-expect-error
triggerSubscriptions(actionSubscriptions, {
args,
name: wrappedAction[ACTION_NAME],
store,
after,
onError,
})
let ret: unknown
try {
ret = fn.apply(this && this.$id === $id ? this : store, args)
// handle sync errors
} catch (error) {
triggerSubscriptions(onErrorCallbackList, error)
throw error
}
if (ret instanceof Promise) {
return ret
.then((value) => {
triggerSubscriptions(afterCallbackList, value)
return value
})
.catch((error) => {
triggerSubscriptions(onErrorCallbackList, error)
return Promise.reject(error)
})
}
// trigger after callbacks
triggerSubscriptions(afterCallbackList, ret)
return ret
} as MarkedAction<Fn>
wrappedAction[ACTION_MARKER] = true
wrappedAction[ACTION_NAME] = name // will be set later
// @ts-expect-error: we are intentionally limiting the returned type to just Fn
// because all the added properties are internals that are exposed through `$onAction()` only
return wrappedAction
}
const _hmrPayload = /*#__PURE__*/ markRaw({
actions: {} as Record<string, any>,
getters: {} as Record<string, Ref>,
state: [] as string[],
hotState,
})
const partialStore = { // 用于记录相关id $patch、Pinia实例 subscribe订阅的方法
_p: pinia,
// _s: scope,
$id,
$onAction: addSubscription.bind(null, actionSubscriptions),
$patch,
$reset,
$subscribe(callback, options = {}) {
const removeSubscription = addSubscription(
subscriptions,
callback,
options.detached,
() => stopWatcher()
)
const stopWatcher = scope.run(() =>
watch(
() => pinia.state.value[$id] as UnwrapRef<S>,
(state) => {
if (options.flush === 'sync' ? isSyncListening : isListening) {
callback(
{
storeId: $id,
type: MutationType.direct,
events: debuggerEvents as DebuggerEvent,
},
state
)
}
},
assign({}, $subscribeOptions, options)
)
)!
return removeSubscription
},
$dispose,
} as _StoreWithState<Id, S, G, A>
/* istanbul ignore if */
if (isVue2) {
// start as non ready
partialStore._r = false
}
const store: Store<Id, S, G, A> = reactive(
__DEV__ || (__USE_DEVTOOLS__ && IS_CLIENT)
? assign(
{
_hmrPayload,
_customProperties: markRaw(new Set<string>()), // devtools custom properties
},
partialStore
// must be added later
// setupStore
)
: partialStore
) as unknown as Store<Id, S, G, A>
// store the partial store now so the setup of stores can instantiate each other before they are finished without
// creating infinite loops.
pinia._s.set($id, store as Store) // 设置store
const runWithContext =
(pinia._a && pinia._a.runWithContext) || fallbackRunWithContext
// TODO: idea create skipSerialize that marks properties as non serializable and they are skipped
const setupStore = runWithContext(() =>
pinia._e.run(() => (scope = effectScope()).run(() => setup({ action }))!)
)!
// overwrite existing actions to support $onAction
for (const key in setupStore) {
const prop = setupStore[key]
if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) {
// mark it as a piece of state to be serialized
if (__DEV__ && hot) {
set(hotState.value, key, toRef(setupStore, key))
// createOptionStore directly sets the state in pinia.state.value so we
// can just skip that
} else if (!isOptionsStore) {
// in setup stores we must hydrate the state and sync pinia state tree with the refs the user just created
if (initialState && shouldHydrate(prop)) {
if (isRef(prop)) {
prop.value = initialState[key as keyof UnwrapRef<S>]
} else {
// probably a reactive object, lets recursively assign
// @ts-expect-error: prop is unknown
mergeReactiveObjects(prop, initialState[key])
}
}
// transfer the ref to the pinia state to keep everything in sync
/* istanbul ignore if */
if (isVue2) {
set(pinia.state.value[$id], key, prop)
} else {
pinia.state.value[$id][key] = prop
}
}
// action
} else if (typeof prop === 'function') {
const actionValue = __DEV__ && hot ? prop : action(prop as _Method, key)
// this a hot module replacement store because the hotUpdate method needs
// to do it with the right context
/* istanbul ignore if */
if (isVue2) {
set(setupStore, key, actionValue)
} else {
// @ts-expect-error
setupStore[key] = actionValue
}
// list actions so they can be used in plugins
// @ts-expect-error
optionsForPlugin.actions[key] = prop
}
}
// add the state, getters, and action properties
/* istanbul ignore if */
if (isVue2) {
Object.keys(setupStore).forEach((key) => {
set(store, key, setupStore[key])
})
} else {
assign(store, setupStore)
// allows retrieving reactive objects with `storeToRefs()`. Must be called after assigning to the reactive object.
// Make `storeToRefs()` work with `reactive()` #799
assign(toRaw(store), setupStore)
}
// use this instead of a computed with setter to be able to create it anywhere
// without linking the computed lifespan to wherever the store is first
// created.
Object.defineProperty(store, '$state', {
get: () => (__DEV__ && hot ? hotState.value : pinia.state.value[$id]),
set: (state) => {
/* istanbul ignore if */
$patch(($state) => {
// @ts-expect-error: FIXME: shouldn't error?
assign($state, state)
})
},
})
// avoid listing internal properties in devtools
;(['_p', '_hmrPayload', '_getters', '_customProperties'] as const).forEach(
(p) => {
Object.defineProperty(
store,
p,
assign({ value: store[p] }, nonEnumerable)
)
}
)
}
/* istanbul ignore if */
if (isVue2) {
// mark the store as ready before plugins
store._r = true
}
// apply all plugins
pinia._p.forEach((extender) => {
/* istanbul ignore else */
assign(
store,
scope.run(() =>
extender({
store: store as Store,
app: pinia._a,
pinia,
options: optionsForPlugin,
})
)!
)
})
// only apply hydrate to option stores with an initial state in pinia
if (
initialState &&
isOptionsStore &&
(options as DefineStoreOptions<Id, S, G, A>).hydrate
) {
;(options as DefineStoreOptions<Id, S, G, A>).hydrate!(
store.$state,
initialState
)
}
isListening = true
isSyncListening = true
return store
}
createOptionsStore
option方式创建,发现最后还是调用了 createSetupStore 创建
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
const initialState: StateTree | undefined = pinia.state.value[id]
let store: Store<Id, S, G, A>
function setup() {
if (!initialState && (!__DEV__ || !hot)) {
/* istanbul ignore if */
if (isVue2) {
set(pinia.state.value, id, state ? state() : {})
} else {
pinia.state.value[id] = state ? state() : {}
}
}
// avoid creating a state in pinia.state.value
const localState =
__DEV__ && hot
? // use ref() to unwrap refs inside state TODO: check if this is still necessary
toRefs(ref(state ? state() : {}).value)
: toRefs(pinia.state.value[id])
return assign(
localState,
actions,
Object.keys(getters || {}).reduce(
(computedGetters, name) => {
computedGetters[name] = markRaw(
computed(() => {
setActivePinia(pinia)
// it was created just before
const store = pinia._s.get(id)!
// allow cross using stores
/* istanbul ignore if */
if (isVue2 && !store._r) return
// @ts-expect-error
// return getters![name].call(context, context)
// TODO: avoid reading the getter while assigning with a global variable
return getters![name].call(store, store)
})
)
return computedGetters
},
{} as Record<string, ComputedRef>
)
)
}
store = createSetupStore(id, setup, options, pinia, hot, true)
return store as any
}
partialStore
const partialStore = {
_p: pinia,
// _s: scope,
$id,
$onAction: addSubscription.bind(null, actionSubscriptions),
$patch,
$reset,
$subscribe(callback, options = {}) {
const removeSubscription = addSubscription(
subscriptions,
callback,
options.detached,
() => stopWatcher()
)
const stopWatcher = scope.run(() =>
watch(
() => pinia.state.value[$id] as UnwrapRef<S>,
(state) => {
if (options.flush === 'sync' ? isSyncListening : isListening) {
callback(
{
storeId: $id,
type: MutationType.direct,
events: debuggerEvents as DebuggerEvent,
},
state
)
}
},
assign({}, $subscribeOptions, options)
)
)!
return removeSubscription
},
$dispose,
} as _StoreWithState<Id, S, G, A>
partialStore内部结构
最后返回的store
总结
- createPinia创建pinia实例,并用effectScope监听响应对象state
- 同时provide注入到全局
- defineStore定义声明store
- 根据传入类型执行createOptionsStore 或 createSetupStore
- createOptionsStore最终调完getters方法还是调用了createSetupStore
- 在createSetupStore 返回一个 reactive的 partialStore 对象
- 内部新增了$patch 方法做数据更新
迷你版实现
createPinia.ts
import { StateTree, StoreGeneric } from "pinia";
import { effectScope, markRaw, ref, Ref } from "vue";
import { App } from "vue-demi";
export function createPinia() {
const scope = effectScope(true);
const state = scope.run<Ref<Record<string, StateTree>>>(() =>
ref<Record<string, StateTree>>({})
)!;
const pinia = markRaw({
install(app: App) {
app.provide(Symbol("my-pinia"), pinia);
},
use() {},
_s: new Map<string, StoreGeneric>(), // 记录所有数据
state,
_e: scope,
});
return pinia;
}
store.ts
import {
computed,
ComputedRef,
effectScope,
EffectScope,
inject,
markRaw,
reactive,
toRaw,
toRefs,
} from "vue";
import { getCurrentInstance } from "vue";
export function defineStore(options: {
id: string;
state: any;
getters: any;
actions: any;
}) {
let { id } = options;
function useStore() {
const currentInstance = getCurrentInstance(); // 获取实例
let pinia: any;
if (currentInstance) {
pinia = inject(Symbol("my-pinia"));
}
if (!pinia._s.has(id)) {
createOptionsStore(id, options, pinia);//第一次初始化
}
const store = pinia._s.get(id); // 缓存中的store全部数据
return store;
}
useStore.$id = id;
return useStore;
}
function createOptionsStore(id: string, options: any, pinia: any) {
const { state, actions, getters } = options;
function setup() {
pinia.state.value[id] = state ? state() : {};
const localState = toRefs(pinia.state.value[id]);
return Object.assign(//属性拷贝
localState,
actions,
Object.keys(getters || {}).reduce((computedGetters, name) => {
computedGetters[name] = markRaw(
computed(() => {
const store = pinia._s.get(id)!;
return getters![name].call(store, store);
})
);
return computedGetters;// 使用computed转化getters方法
}, {} as Record<string, ComputedRef>)
);
}
let store = createSetupStore(id, setup, pinia);
store.$reset = function $reset() {
const newState = state ? state() : {};
this.$patch(($state: any) => {
Object.assign($state, newState);
});
};
return store;
}
function createSetupStore($id: string, setup: any, pinia: any) { // setup为传入的setup方法
// 所有信息 包括订阅发布的subscribe
let partialStore = {
_p: pinia,
$id,
$reset: () => {},
$patch,
$dispose,
// $subscribe
};
// 将effect数据存放如pinia._e、setupStore
let scope!: EffectScope;
const setupStore = pinia._e.run(() => {
scope = effectScope();
return scope.run(() => setup());//这里执行的setup 会被响应式收集管理
});
// 属性方法合并
const store: any = reactive(
Object.assign(toRaw({}), partialStore, setupStore)
);
// pinia大对象 设置每个小store
pinia._s.set($id, store);
function $patch(partialStateOrMutator: any): void { // 更新当前状态
if (typeof partialStateOrMutator === "function") {
partialStateOrMutator(pinia.state.value[$id]);
}
}
function $dispose() {
scope.stop();
pinia._s.delete($id);
}
return store;
}