参考资料 pinia的使用(原链接) Pinia支持compositionApi同时也兼容optionsApi(this指向) Pinia可以采用多个store,store之间可以互相调用(扁平化),不用担心命名冲突问题
Pinia基本使用 组合式 与 选项式 写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import {defineStore} from '../pinia/store' import {ref, computed} from 'vue' export const useCounterStore = defineStore('counter', ()=>{ const count = ref(0) const double = computed(()=>{ return count.value * 2 }) const increment = (num)=>{ count.value += num console.log(todosStore.todos) } return { count, double, increment } }) // export const useCounterStore = defineStore('counter', { // state: () => { // return {count: 0} // }, // getters: { // double() { // return this.count * 2 // } // }, // actions: { // increment(num) { // this.count += num // } // } // })
Pinia实现原理 pinia中的最近数据存储在state中_s
记录了state的各个时态, 存放各个时期的state, 有利于重复使用
创建原理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import { PiniaSymbol } from "./rootStore" import {ref} from 'vue' export function createPinia () { const state = ref({}) const pinia = { install(app) { app.config.globalProperties.$pinia = pinia //更新 app.provide(PiniaSymbol, pinia) }, state, _s:new Map(), } return pinia }
存储原理 其中参数含义如下:
getCurrentInstance
是 Vue 3 Composition API 中的一个函数,用于获取当前组件实例的内部对象
在调用useStore时, 首先会在_s
中寻找所求值是否存在, 如果存在则直接赋值, 不存在则再创建一个新的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 export function defineStore(idOrOptions, setup) { let id let options //判断是组合式还是选项式 const isSetupStore = typeof setup === 'function' if(typeof idOrOptions == 'string') { id = idOrOptions options = setup } else { //不是字符串, 说明传入了对象 options = idOrOptions id = idOrOptions.id } function useStore() { const currentInstance = getCurrentInstance() const pinia = currentInstance && inject(PiniaSymbol) if (!pinia._s.has(id)) { //这个store时第一次使用 if(isSetupStore){ createSetupStore(id, setup, pinia) //创建组合式store } else { createOptionStore(id, options, pinia) //创建后的store只需要存到_s中即可 } } //如果已经有了store, 不用创建, 直接拿到store就行了 const store = pinia._s.get(id) return store } return useStore }
组合式 this指向 当传入的值中含有方法对象时, 直接处理会改变对象的this指向, 这时候需要进行特殊处理 保证传入的类的this指向不改变 通过wrapAction函数显示转换他们的this指向
将处理好的值与旧值进行更新 (Object.assign
) 加入到_s
中, 在二次调用时减少计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 //setupStore 用户已经提供了完整的setup方法了, 我们只需执行setup函数即可, //通过这个返回值, 将其放在store上即可 function createSetupStore(id, setup, pinia) { const store = reactive({}) //处理this指向 function wrapAction (action) { return function () { //将action中的this永远处理成store, 保证this指向正确 return action.call(store, ...arguments) } } const setupStore = setup() //拿到的setupStore可能没有处理this指向 //处理this for (let prop in setupStore) { const value = setupStore[prop] if (typeof value === 'function') { setupStore[prop] = wrapAction(value) } } Object.assign(store, setupStore)//加入到队伍里面 pinia._s.set(id, store) return store }
选项式 相比与组合式, 选项式多了一个setup()函数
在此过程中,state 的内容也会被存储到 pinia.state
中。action 则会被 wrapAction
处理
每一项 action 进行处理,目的是为了支持 $onAction
方法,此方法会在执行 action 时执行回调函数,回调函数可以接收三个参数分别是:被调用的 store 、action 的名字 、传递给 action 的参数 。
getters处理时, 需将他的逻辑执行完后,得到的值存入store
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function createOptionStore(id, options, pinia) { const {state, actions, getters={}} = options //store里面的数据, 已定义 const store = reactive({}) //pinia就是创建了一个响应式对象而已 //需要将这个api转换成组合式 function setup() { pinia.state.value[id] = state ? state() : {} let localState = toRefs(pinia.state.value[id]) return Object.assign( localState, actions, Object.keys(getters).reduce((computeds, getterKey)=>{ computeds[getterKey] = computed(()=>{ return getters[getterKey].call(store) }) return computeds }, {}) ) } //减少代码的重复书写, 此时选项式store已经将数据处理, 剩下的逻辑和处理组合式一致 createSetupStore(id, setup, pinia) return store }
store中的基础api实现 定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const actionSubscriptions = [] //所有订阅的actions时间, 都应该放在这个数组中 const partialStore = { $patch, //订阅状态 $subscribe(callback) { watch(pinia.state.value[id], state => { callback({id}, state) }) }, //订阅用户的action操作 $onAction: addSubscription.bind(null, actionSubscriptions), //订阅 } //pinia就是创建了一个响应式对象而已 const store = reactive(partialStore)
patch 将一个 state 补丁应用于当前状态。允许传递嵌套值。
$patch
允许两种参数传递方式,传入一个函数,或一个 state 的补丁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 //这里需要获取原来的所有状态 function $patch(partialStateOrMutator) { //partialStateOrMutator部分状态 if(typeof partialStateOrMutator !== 'function') { //当前store中的全部状态, pinia.state.value[i] merge(pinia.state.value[id], partialStateOrMutator) } else { partialStateOrMutator(pinia.state.value[id]) } } function merge(target, partialState) { for (const key in partialState) { if(!partialState.hasOwnProperty(key)) continue //原始的值 const targetValue = target[key] //后来的值 const subPatch = partialState[key] if (isObject(targetValue) && isObject(subPatch) && !isRef(subPatch)) { //ref也是对象 target[key] = merge(targetValue, subPatch) } else { //如果不需要合并直接用新的覆盖掉老的即可 target[key] = subPatch } } } if(isSetupStore) { pinia.state.value[id] = {} //用于存放setupStore中的状态 }
reset 功能 : 通过建立一个新的状态对象,将 store 重设为初始状态。
重置方法只能在optionStore中书写 因为在setupStore中, 用户自己控制状态;
state: () => ({count: 1}) 是一个函数,只要重新调用就可以获取原始值,
而 组合式 构建的话 state 以 ref() 的形式实现,无法获取原始值。
1 2 3 4 5 6 7 store.$reset = function() { //这个方法只支持optionAPI, 因为setupAPI中, 用户自己控制状态 const newState = state ? state() : {} this.$patch(newState) }
subscribe 设置一个回调,当状态发生变化时被调用。它会返回一个用来移除此回调的函数。 请注意,当在组件内调用 store.$subscribe()
时,除非 detached
被设置为 true, 否则当组件被卸载时,它将被自动清理掉。
在store中的实现
1 2 3 4 5 6 7 8 9 10 11 //订阅状态 $subscribe(callback) { watch(pinia.state.value[id], state => { callback({id}, state) }) },
onAction 设置一个回调,当一个 action 即将被调用时,就会被调用。 回调接收一个对象, 其包含被调用 action 的所有相关信息:
store
: 被调用的 store
name
: action 的名称
args
: 传递给 action 的参数
除此之外,它会接收两个函数, 允许在 action 完成或失败时执行的回调。
它还会返回一个用来删除回调的函数。 请注意,当在组件内调用 store.$onAction()
时,除非 detached
被设置为 true, 否则当组件被卸载时,它将被自动清理掉。
调用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 export function addSubscription(subscriptions, callback) { subscriptions.push(callback)//将回调函数放在数组中 const removeSubscription = () => { const idx = subscriptions.indexOf(callback) if(idx > -1) { subscriptions.splice(idx, 1) } } return removeSubscription } export function triggerSubscriptions(subscriptions, ...args) { subscriptions.slice().forEach(cb=>cb(...args)) }
定义
1 2 3 4 5 6 7 8 9 const actionSubscriptions = [] //所有订阅的actions时间, 都应该放在这个数组中 const partialStore = { ... //订阅用户的action操作 $onAction: addSubscription.bind(null, actionSubscriptions), }
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 //处理this指向 function wrapAction (action) { return function () { const afterCallbacks = [] const onErrorCallbacks = [] const after = (callback) => { afterCallbacks.push(callback) } const onError = (callback) => { onErrorCallbacks.push(callback) } triggerSubscriptions(actionSubscriptions, {after, onError}) //让用户传递after和error //将action中的this永远处理成store, 保证this指向正确 //回调的方式 let result try {//正常action是一个回调的情况, 可以直接拿到返回值出发after回调 result = action.call(store, ...arguments) triggerSubscriptions(afterCallbacks, result) } catch (e){ triggerSubscriptions(onErrorCallbacks, e) } //如果返回值是一个Promise, 针对场景做处理 if(result instanceof Promise) { return result.then(value => { triggerSubscriptions(afterCallbacks, value) }).catch(error => { triggerSubscriptions(onErrorCallbacks, error) }) } return result } }
pinia插件写法 main.js中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 const pinia = createPinia() function persitisPlugin() { //为了用户传递参数, 所以用函数 return ({store, id}) => { //所有的store都会执行此方法 let oldState = JSON.parse(localStorage.getItem(id) || '{}') store.$state = oldState // store.$patch(oldState) //将老状态进行替换 store.$subscribe((mutation, state) => { localStorage.setItem(id, JSON.stringify(state)) }) } } pinia.use(persitisPlugin()) //use API可以去调用插件的install方法,将app注入进来 app.use(pinia) //使用pinia插件
createStore中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import { PiniaSymbol } from "./rootStore" import {ref} from 'vue' export function createPinia () { //pinia是管理多个state状态的 const state = ref({}) const _p = [] //插件 const pinia = { install(app) { app.config.globalProperties.$pinia = pinia app.provide(PiniaSymbol, pinia) }, use(plugin) { _p.push(plugin) return pinia }, state, _s:new Map(), _p } return pinia }
总结 仓库链接 这里附上整个源码