index.ts
主要就是两个方法
export { createPinia } from "./createPinia"
export {defineStore} from './defineStore'
主要就是为了返回一个带install方法的对象,然后install的时候向全局暴露出pinia这个对象(这个对象不管在vue3中还是vue2中还是在其他不是vue的组件中都能使用),这个对象里有一个存放了所有store的_s
属性,有用来停止所有state的响应式的_e
属性,还有一个存放所有state 的state属性,还有插件列表和提供给外界注册插件的函数。
import {ref,effectScope} from 'vue'
import { piniaSymbol } from './piniaSymbol'
export let activePinia //通过这个全局变量来存放piniaStore,为了在不是vue组件中也能访问到piniaStore(比如router)。
export const setActivePinia = (piniaStore) => activePinia = piniaStore
export function createPinia() { //是一个插件方法,返回一个带install方法的对象。
const scope = effectScope(true) // 创建一个依赖函数集,到时候方便一起暂停他们的响应式。
const state = scope.run(()=>ref({})) //存放所有state。通过scope.run去包一层方便后续通过scope.stop()这个方法来全部暂停这些的响应式。
const _plugins = [] //存放所有的插件(其实就是函数)
const piniaStore = {
use(plugin) { // 提供给外界用于注册插件
_plugins.push(plugin)
return this //返回this方便链式调用
},
_plugins,
_stores: new Map(), //存放所有的store
_e:scope, //用来停止所有state的响应式。(实际上pinia并没有提供停止所有 响应式的方法,但是我们可以在pinia中可以使用store1._p._e.stop()来终止所有effect,但当然pinia是不推荐这样做的(注:store1是指一个store实例))
state,
install(app) {
setActivePinia(piniaStore) //将这个piniaStore暴露到全局上,为了在不是vue组件中也能访问到piniaStore(比如router)。
app.provide(piniaSymbol, piniaStore) //这样就能让vue3的所有组件都可以通过app.inject(piniaSymbol)访问到piniaStore
app.config.globalProperties.$pinia = piniaStore //这样就能让vue2的组件实例也可以共享piniaStore
}
}
return piniaStore
}
defineStore接受三种传参方式: (感觉传入options就是为了迎合vue2的写法)
所以进来这个函数第一件事就是处理第一个参数idOrOptions
,把id和options分别提取出来。
然后返回一个useStore函数,这个函数里会获取整个pinia实例(即上面createPinia暴露出全局的对象),然后看里面有没有目前使用的store,没有就创建,然后把这个store返回给用户。(所以创建store是在use的时候完成的而不是define的时候)。
import {piniaSymbol} from './piniaSymbol'
import {getCurrentInstance,inject,reactive,effectScope,isRef,isReactive} from 'vue'
import { activePinia, setActivePinia } from './createPinia'
/**
* defineStore接受三种传参方式: (感觉传入options就是为了迎合vue2的写法)
* 第一种是传入id + options。
* 第二种是只传入options(id也包含在这里面)
* 第三种是传入id + setup函数
*/
export function defineStore(idOrOptions, optionsOrSetup) {
let id, options
//处理第一个参数 idOrOptions
if (typeof idOrOptions === 'string') { //是第一种传参方式
id = idOrOptions
options = optionsOrSetup
} else { // 是第二种传参方式
options = idOrOptions
id = options.id
}
function useStore() {
const instance = getCurrentInstance() // 获得当前组件实例
let piniaStore = instance && inject(piniaSymbol) //如果当前组件实例存在就注入整个piniaStore(因为只有在vue组件里才能使用inject)
if (piniaStore) {
setActivePinia(piniaStore)
}
piniaStore = activePinia //这样就可以哪怕不是在vue组件中使用也能拿到整个piniaStore(比如在router中使用)
if (!piniaStore._stores.has(id)) { //如果是还没有这个store(即第一次使用这个useStore),那就创建这个store。!!在use的时候才会创建这个store!!
if (typeof optionsOrSetup === 'function') { //传进来一个setup函数 ,是第三种传参方式
createSetupStore(id, optionsOrSetup,piniaStore)
} else { //前两种传参方式都用这个来构建store
createOptionsStore(id,options,piniaStore)
}
}
return piniaStore._stores.get(id) //获得目前use的这个store
}
return useStore //用户使用这个函数就能获得这个store
}
创建store有两种方式(一种是用户在defineStore里传入了options,一种是用户在defineStore里传入了setup),但其实最后都是靠createSetupStore来创建store,createOptionsStore只不过是把参数封装成setup的形式然后再传给createSetupStore。
_s
属性里。 $reset
方法(这个方法只有options定义的store才有)。 function isComputed(v) { // 计算属性是ref,同时也是一个effect
return !!(isRef(v)&&v.effect)
}
/**defineStore传入了setup函数时调用这个函数
* id 表示store的id
* setup表示setup函数
* piniaStore表示整个pinia的store
* isOption表示用户是否用option语法define的store
*/
function createSetupStore(id, setup, piniaStore,isOption) {
let scope
function $patch(){}
const partialStore = {//内置的api存放到这个store里
$patch
}
const store = reactive(partialStore) //store就是一个响应式对象,这个是最后暴露出去的store,会存放内置的api和用户定义的store
if (!piniaStore.state.value[id] && !isOption) { // 整个pinia的store里还没有存放过目前这个state 且 用户用options语法来define的store
piniaStore.state.value[id] = {}
}
//这个函数就是为了到时候方便停止响应式。(核心的创建store可以不要这部分代码)
const setupStore = piniaStore._e.run(() => { //这样包一层就可以到时候通过pinia.store.stop()来停止全部store的响应式
scope = effectScope()
return scope.run(()=>setup()) //这样包一层就可以到时候通过scope.stop()来停止这个store的响应式
})
//遍历这个store里的所有属性,做进一步处理
for (let key in setupStore) {
const prop = setupStore[key]
//处理action
if (typeof prop == 'function') {
setupStore[key] = wrapAction(key, prop)
}
//处理state
if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) { //如果他是ref或者是reactive则说明它是state(注意由于computed也是ref,所以要排除掉计算属性)
if (!isOption) { //如果是setup语法,把里面的state也存到全局的state里
piniaStore.state.value[id][key] = prop
}
}
}
/**对actions包一层,做一些处理。store里面存的actions实际都是经过了这个包装的actions。*/
function wrapAction(name, action) {
return function () {
let ret = action.apply(store, arguments) //使this永远指向store
//action执行后可能是一个promise,todo......
return ret
}
}
// 把不是用户定义的和是用户定义的都合并到store里,并给外面使用
Object.assign(store,setupStore)
piniaStore._stores.set(id, store)//将这个store存到piniaStore中
return store
}
/**defineStore传入了options时调用这个函数 (感觉传入options就是为了迎合vue2的写法)*/
function createOptionsStore(id, options,piniaStore) {
const { state, actions, getters } = options
function setup() { //处理store里的state、actions、getters
piniaStore.state.value[id] = state ? state() : {} //把这个store的state存到piniaStore里
const localState = toRefs(piniaStore.state.value[id]) //把这个store的state转换成ref即变成响应式,因为options写法里的state并不是响应式的。
return Object.assign( //这里返回的对象就是用户存放用户定义的属性和方法
localState, //用户的state
actions, // 用户的actions
Object.keys(getters || {}).reduce((memo, name) => { //用户的getters,因为用户的getters这个对象里的属性都是函数,所以我要把这些函数都执行了变成计算属性
memo[name] = computed(() => {
let store = pinia._stores.get(id)
return getters[name].call(store)
})//call是为了保证this指向store
},{}))
}
const store = createSetupStore(id, setup, piniaStore, true)
store.$reset = function () {
const rawState = state ? state() : {}
store.$patch(state => {
Object.assign(state,rawState)
})
}
}
这些api其实是在上面createSetupStore函数里编写的(除了$reset
方法只有options定义的store才有,所以就写在上面createOptionsStore里了),这里抽离出来方便观看。
function createSetupStore(id, setup, piniaStore,isOption) {
//...其他代码...
/**此函数用于批量修改state,有两种传参方式:一种是传入一个对象,这个对象里有部分或全部state;另一种是传入一个函数,这个函数的参数是state,函数体对state进行修改 */
function $patch(partialStateOrMutator) {
if (typeof partialStateOrMutator === 'object') {
mergeReactiveObject(pinia.state.value[id],partialStateOrMutator) //递归合并两个对象
} else { //partialStateOrMutator是一个function
partialStateOrMutator(pinia.state.value[id])
}
}
//...其他代码...
}
function mergeReactiveObject(target, state) { //递归合并两个对象
for (let key in state) {
let oldValue = target[key];
let newValue = state[key];
if (oldValue && newValue && oldValue.constructor === Object && newValue.constructor === Object) { // 两个都是对象
target[key] = mergeReactiveObject(oldValue, newValue);
} else {
target[key] = newValue;
}
}
return target;
}
function createSetupStore(id, setup, piniaStore,isOption) {
//...其他代码...
/**此函数用于在state发生变化的时候执行个函数,原理就是利用vue提供的watch去监听state变化。(套娃)
* callback: 在state变化时要执行的函数。这个callback的参数是store的id和state。
* options: 就是vue里watch需要的options参数
*/
function $subscribe(callback, options = {}) {
scope.run(() => watch(piniaStore.state.value[id], state => { //scope.run包一层纯粹就是为了到时候便于停止响应式,没有其他任何实际作用。
callback({storeId:id},state)
},options))
}
//...其他代码...
}
function createSetupStore(id, setup, piniaStore,isOption) {
//...其他代码...
const actionSubscribtions = []//存放action执行之前的订阅函数
/**此函数用于订阅一个函数在触发action之前或action执行之后或action发生错误的时候执行。参数是一个callback,这个callback里有after或onError的函数参数,整个callback会在action执行之前执行。 */
function $onAction(callback) {
addSubscribtion.bind(null,actionSubscribtions)(callback)
}
// 为了能触发订阅的函数 对createSetupStore里的wrapAction进行补充:
/**对actions包一层,做一些处理。store里面存的actions实际都是经过了这个包装的actions。*/
function wrapAction(name, action) {
return function () {
// 存放action的之后和发生错误后的订阅函数
const afterCallbackList = []
const onErrorCallbackList = []
function after(callback) {
afterCallbackList.push(callback)
}
function onError(callback) {
onErrorCallbackList.push(callback)
}
triggerSubscribtions(actionSubscribtions, { after, onError }) //触发action执行前的订阅函数
let ret
try {
ret = action.apply(store, arguments) //使this永远指向store 并执行action!!!
} catch(e) {
triggerSubscribtions(onErrorCallbackList, e) //触发action执行错误后的订阅
}
if (ret instanceof Promise) { //如果action是promise
return ret.then(value => {
return triggerSubscribtions(afterCallbackList,value)
}).catch(e => {
triggerSubscribtions(onErrorCallbackList,e)
return Promise.reject(e)
})
}
triggerSubscribtions(afterCallbackList, ret) //触发action执行后的订阅
return ret
}
}
//...其他代码...
}
function createSetupStore(id, setup, piniaStore,isOption) {
//...其他代码...
/**此函数用于Stops the associated effect scope of the store and remove it from the store registry. */
function $dispose() {
scope.stop() //清除响应式
actionSubscribtions = [] //取消订阅
piniaStore._stores.delete(id) //清除这个store
}
//...其他代码...
}
function createSetupStore(id, setup, piniaStore,isOption) {
//...其他代码...
Object.defineProperties(store, '$state', { //给store添加一个属性$state。Setting it will replace the whole state.
get: () => pinia.state.value[id],
set:state=>$patch($state=>Object.assign($state,state))
})
//...其他代码...
}
所有插件都会存到总的pinia上,详情见createPinia
每次创建store都会调用插件方法。
function createSetupStore(id, setup, piniaStore,isOption) {
//...其他代码...
pinia._plugins.forEach(plugin => {
Object.assign(store,scope.run(()=>plugin({store}))) //Object.assign是为了让插件的返回值作为store的属性
})
//...其他代码...
}
import { toRaw, toRef, isRef ,isReactive} from "vue"
export function storeToRefs(store) { // 作用是跟toRefs一样的,只不过toRefs会处理函数的情况,于是pinia就写一个只处理响应式对象的。原理就是使用toRef。
store = toRaw(store)
const refs = {}
for (let key in store) {
const value = store[key]
if (isRef(value)||isReactive(value)) {
refs[key] = toRef(store,key)
}
}
return refs
}
本文由 mdnice 多平台发布