昨天我们看了cretePinia
的逻辑,了解了初始化的逻辑。
今天我们来看下defineStore
。
昨天我们用的测试用例是没有数据的,今天我们换一个:
这是官方提供的:
不过我一般的写法是setup stores
:
所以我这里修改下测试用例
前面一大段的函数重载
我们来看下核心代码:
代码量不多,就不做删减了。
idOrOptions
:我们defineStore
的第一个参数,作为唯一标识符。setup
:我们传入的第二个参数,我们这里传入了一个函数:isSetupStore
: 不多说,我们的写法自然就是setup store
,注意,只要传入函数就会被认定是setup store
的写法。最后它返回一个useStore
的函数,并把我们的唯一标识符赋值给这个函数$id
。
而这个useStore
的函数就是我们业务代码中引入的hook
,我们需要执行这个hook
才能拿到最终的数据:
这里我们不需要传入pinia
作为参数,因为我们有全局activePinia
。
然后我们来看下useStore
执行的逻辑。
hasInjectionContext
:这个函数来自于vue(vue-demi)
:inject
逻辑,前面一章我们说过了currentInstance
、currentRenderingInstsance
的作用以及provide/inject
的实现逻辑,所以这里不多说。pinia = inject(piniaSymbol, null)
:没啥好说的,从当前组件实例中拿到pinia
,前面一章我们知道了install
的时候这货会用vue
的provide
将自己注入所有组件实例中,所以理论上是拿得到的。if (pinia) setActivePinia(pinia)
:这里是将全局的activePinia
设置成当前组件实例绑定的pinia
实例。你可呢在疑惑为什么要这么做,install
到vue
根组件的时候不是已经这么做了吗?为什么还要再设置一次?因为不一定只有一个vue
根组件,也不一定只有一个pinia
实例。可能存在两个pinia实例
,比如:pinia
实例,另外这个项目中可能存在多个vue
根组件实例,每个实例单独use
一个pinia实例
。但是我们只有一个activePinia
位置!这玩意儿全局只有一个!。那么这个时候activePinia
指向就不一定准了。pinia._s
:_s: Map<string, StoreGeneric>
,存放的是defineStore
里的数据,key
自然就是defineStore
第一个参数。createSetupStore
: 这玩意儿代码有些长。。我们等会开个小章节说下,这里先跳过createOptionsStore
:这个和createSetupStore
放到一起说,所以这里也先跳过const store: StoreGeneric = pinia._s.get(id)!
:这货就是我们useXX
返回的数据,指向我们定义的store
数据剩余部分的代码是和开发阶段热更新有关的,官方链接:HMR (Hot Module Replacement) | Pinia (vuejs.org)
这里简单说下:如果是vite
,那么开箱即用,vite
支持自定义热更新内容(当然,webpack等也支持,不过写法不一样):HMR API | Vite (vitejs.dev)
这么做就可以搭vite
热更新的车了。
具体如何实现的,因为涉及到vite
热更新的原理,我们以后再分析,现阶段先跳过。
这货代码较长,我这里就不贴整体的了,我们拆开了一部分一部分分析:
函数整体:
可以看到最终是返回一个reactive
包裹的响应式store
数据,这个数据就是最终我们useXX
返回的数据,对应前面useStore
函数的返回store
。
然后我们来看下这个partialStore
:
_p
:这里注意和pinia实例
的_p
区分开来,pinia实例
的_p
指向pinia
的插件。这里则指向pinia实例
。然后没啥好说的了,这就是在初始化一个store
实例。
我们接着看下每个property
是干啥用的。
$onAction
首先是犯下傲慢的$onAction
:Interface: _StoreWithState | Pinia (vuejs.org)
就是一个简单的订阅和取消订阅的方法,这里只是订阅,还没涉及到发布。
所以这个$onAction
就是用来订阅的,另外根据官方文档的描述:
Setups a callback to be called every time an action is about to get invoked
这货可以用在每次action
触发后的回调。
返回一个函数用来手动注销回调。
看下基础用法:
然后我们再来看下$patch
$patch
Interface: _StoreWithState | Pinia (vuejs.org)
在看代码之前,我们先看下基础用法:
然后看下代码
代码稍微复杂了点。
首先,如果传入的是函数,那么会将store
实例的state
传递给我们传入和函数,否则直接合并俩state
。。前面一直没有解释类型,因为可读性较差,另外是觉得没必要,现在是时候看下UnwrapRef<T>
了:
不是我不想解释,实在是太类型体操了。
简单的说下UnwrapRef<T>
的作用,就是用来取消ref
包裹数据的那一层。
原本我们需要调用.value
才能访问到这个数据,使用UnwrapRef<T>
之后就不需要了。
来看下官方的例子:Reactivity Fundamentals | Vue.js (vuejs.org)
当它被赋值给一个reactive
的属性的时候它会自动解除外面那一层。
前面我们知道了usexx
返回的就是一个reactive
的store
实例,所以这里state
自然就是UnwrapRef
。
不过这里你可能会有疑惑,为什么传给我们回调的是pinia.state.value[$id]
这就需要我们回到createPinia
那里:
这里的state
就是上面的pinia.state
,因为pinia
本身是raw
的,所以state
并不能变成UnwrapRef
。
ok
,但是这里还有个问题,初始化的时候是空的,这里却是从里面拿出store
来,这不对啊。
实际上前面还有逻辑我没给出:
就是在这里,因为我们传给$patch
的是函数,所以我们是自己忘state
上面加数据的,所以这里只需要判断是否为空,为空初始化这个实例在state
上的数据即可。
那么到这我们就知道了为什么这里是从pinia.state.value[$id]
中拿到的数据。
貌似扯的有些远了,我们回到之前的代码,在执行完我们传入的函数之后。
我们前面看的$onAction
就是设置回调可以在每次action
之后执行回调,那么这里我们在更新数据之后就需要触发回调(pinia
只有action
)。
则例isListening
和isSyncListening
俩参数暂时还不清楚是干什么用的,所以先暂时搁置,后面遇到再解释。
triggerSubscriptions
:就是简单的执行回调,注意是slice
是clone
一个,而不是直接占用。
这里args
里有俩参数:
注意这里第一个参数里有一个debuggerEvents
,只有开发阶段才能使用,我们可以往里面传入debug
回调。
相关链接:Interface: SubscriptionCallbackMutationPatchFunction | Pinia (vuejs.org)
到这我们暂时只知道如何存储,还不知道如何调用,等会遇到了我们再说。
ok, $patch
分析到这,我们有俩个点还没分析:
然后我们来看下$reset
$reset
Interface: _StoreWithState | Pinia (vuejs.org)
Resets the store to its initial state by building a new state object.
不多说,直接看代码
可以看到只有使用optionStore
的时候才可行,为什么呢?
因为我们用的是setup
语法,没有state
函数。
总之,setupStore
需要开发者手动实现初始化函数,因为不像optionStore
一样是一个对象很好管理。
然后我们来看下$subscribe
$subscribe
Interface: _StoreWithState | Pinia (vuejs.org)
Setups a callback to be called whenever the state changes. It also returns a function to remove the callback. Note that when calling
store.$subscribe()
inside of a component, it will be automatically cleaned up when the component gets unmounted unlessdetached
is set to true.
$onAction
不同的是这里还传入了一个() => stopWatcher()
作为addSubscription
的第四个参数onCleanup
,在执行函数返回的函数即注销回调会执行这个onCleanup
。触发stopWatcher
stopWatcher
中有个scope
,我们来看下这货是从哪来的:_a
:这货前面说过了,就是这个pinia实例
注册的那个vue
根实例runWithContext
:这个则是vue
里的函数,用来确保当前执行函数的上下文是对应的vue
根实例。_e
: EffectScope
,这货是pinia实例
初始化即createPinia
时触发的effectscope
。简单的说就是创建一个当前store
的effectScope
,用来收集store
(我们传入的setup
函数)里的响应式数据
然后回到stopWatcher
中,scope
收集一个watch
,watch
的作用是只要当前store实例
响应式数据发生变化就执行回调。注意是当前store实例
,而$onAction
则是任一store实例执行action(即$patch)
之后。
这里我们遇到了前面没说的isSyncListening
和isListening
字段
然后我们结合$patch
里的逻辑,整理下:
这里需要补充watch
的知识:Watchers | Vue.js (vuejs.org)
简单的说,就是用来解决大数据量操作时同步触发大量watch
回调的问题,所以vue
针对这点增加了watch
的这一特性,可以让开发者定义什么时候执行watch
回调。
默认情况下,watch
回调是在父组件更新后子组件DOM
更新前触发(flush: sync),这么做是避免watch
里改动数据引起重复渲染。
当然,如果你有这方面的想法,想在子组件(当前组件)更新之后再执行,可以设置flush: post
:
或者说在这之前获取:
ok
,回到我们的代码中。在知道了watch
可以手动调整执行回调timing
之后,我们知道了这里nextTick + activeListener === myListenerId
的作用,就是用来判断当前是否是flush: post
执行,如果是,那么isListening = true
。
而$subscribe
里的watch
只能在flush
和post
两种情况下执行,记录isListening
是确保不会执行错回调,毕竟这个回调只能在这个store实例
数据刷新后执行。
ok,还剩最后一个$dispose
$dispose
Interface: _StoreWithState | Pinia (vuejs.org)
这货不用多说,就是remove
这个store实例
。
ok,跳出partialStrore
回到我们的createSetupStore
函数中
在执行了runWithContext
之后我们拿到了setupStore
,为了方便分析,我加了点其它的东西
然后我们接着看代码(部分dev
和hmr
相关的代码省略):
shouldHydrate
:是否需要注入,如果是对象并且不在store
中,那就需要合并mergeReactiveObjects
:篇幅问题,这里就不贴代码了,就是在合并响应式数据wrapAction
:这货得看下代码:普通函数
的。简单的说就是包裹一层普通函数,让它也能触发action
回调以及函数自己的回调。然后继续回到createSetupStore
中,最终
注意这里的store
是前面基于partialStore
创建的store
,没有数据的,而setupStore
则是数据。
接着我们继续往下看:
这是在触发插件并把结果赋值给store
。
最后一部分:
这里为什么要把isListening
和isSyncListening
置为true
,因为我们的$patch
和$subscribe
以及watch
回调实际上是闭包,如果这里不变成true
,那么等这个函数出栈,里面的逻辑就有问题了。
ok,createSetupStore
总算是讲完了,我们整理下。
简单整理createSetupStore
partialStore(里面包含比如$patch的api)
创建reactive store实例
,然后将它放入pinia._s(Map<string, StoreGeneric>)
中。vue.runWithContext
在这个pinia实例
对应的vue
上下文中执行setup
函数获得我们的setupStore
也就是store
数据setupStore
,如果是普通函数则包裹一层用于触发action
。setupStore
给store实例
store实例
isListening
和isSyncListening
置为true
,避免函数推出调用栈导致闭包中引用有问题store实例
。然后我们再来讲下createOptionsStore
这货就比较简单了,因为格式有板有眼,直接贴代码:
这里唯一要说的是computedGetter
。
先看下getter
的基础用法:
回归性原理:getters
就是computed
!
这里还有一点就是markRaw
,说明getter
数据改动不会引起响应式更新。
其它没啥好说的了。。。
我是没想到这一章内容这么多。。。。
简单总结下defineStore
里做了什么:
setupStore
还是optionsStore
useStore
函数,这个函数就是我们业务代码调用时的const xx = useXX()
的useXX
。useStore
里首先将上下文调整为符合当前pinia实例
的vue
根实例setupStore
分别调用createSetupStore
和createOptionsStore
store实例
突然发现没了,我们要分析的内容到这里就结束了~