# my-pinia **Repository Path**: bibinocode/my-pinia ## Basic Information - **Project Name**: my-pinia - **Description**: 学习pinia源码,并建议进行实现 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2023-02-19 - **Last Updated**: 2023-02-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # my-pinia - 学习并实现mini-pinia ## createPinia - vue的插件定义为一个函数,返回一个对象包含install(app)方法,app即vue示例 - 对于pinia 内部来说 默认是多仓库(结构存储),并且在组件上可以进行store访问(inject/provide) - 对于vue来说 可以通过$pinia拿到pinia实例,并且pinia应挂载一个_s(存储所有的store),_e(调用卸载时,用于存放停止响应) - install 接口实现 - 让所有vue组件获取到pinia(provide)注入,包含vue2获取globalProperties 挂载 - 对于停止响应式,effect虽然可以通过拿到函数返回值然后调用`x.effect.stop()`停止,但是如果我们有很多个状态,那么就无法批量管理。因此引入vue3的函数`effectScope`可以批量管理底层为effect实现的api的响应式丢失(ref,reactive,watch,watchEffect等)。 ```js import {reactive,effect,effectScope} from 'vue' const state = reactive({name:'ab'}) const scope = effectScope() scope.run(()=>effect(()=>console.log(state.name))) scope.stop() state.name = 'abyyds' // 丢失响应 ``` - 因为state状态内,可能存在computed计算属性。我们希望调用scope._e.stop()停止所有响应 - 完整代码 ```js // src/pinia/createPinia.js import {ref,effectScope} from 'vue' const piniaSymbol = Symobl() export function createPinia(){ const scope = effectScope() // - const state = ref({}) const state = scope.run(()=>ref({})) const pinia = { _s:new Map(), // 用于存放所有store {sotrea->storea,storeb->storeb} _e:scope, install(app)=>{ // 实现插件安装接口 app.provide(piniaSymbol,pinia) // 组件通过app.inject(piniaSymbol) 获取到pinia app.config.globalProperties.$pinia = pinia // 让vue2通过$pinia获取 }, state } return pinia } ``` ## defineStore - 支持两种写法,options和setup(配置,函数) - 通过defineStore传入选项{state,getters,actions},然后想办法将其挂载pinia的state中 - pinia的state是ref定义的因此可以响应时,然后defineStore 内有useStore方法获取当前的store - 需要解决的问题: - 如果当前仓库没存在$pinia.state中 需要创建,否则取出 - 处理多种类型的定义方式 - 对于this指向永远保持于当前store - 父级可以停止所有的响应时,store自己也可以停止自己的响应时 - actions 和 getters 和 state的映射 - 如何获取到全局pinia ### 初步版本实现options方式的 ```js import {piniaSymbol} from './rootState' import {getCurrentInstance,inject,reactive,effectScope,computed} from 'vue' // 1. 先处理命名空间如果是{id:''} 或者 字符串 // 2. 返回一个函数,用户拿到后调用可以获取到store // 3. 通过getCurrentInstance 获取app组件实例,然后通过inject然后获取pinia实例 // 4. 通过pinia._s 来获取仓库 如果当前仓库不存在进行初始化 如果存在进行获取返回 // 5. 处理仓库初始化的操作 创建一个createOptions 进行仓库映射 // 6. 对于全局pinia可以直接调用_e.stop停止所有仓库响应时,store可以自己停止自己 // 7. 创建局部仓库映射到全局_s存储中,并返回。通过reactive包裹 // 8. 处理局部仓库内容,然后添加到仓库中 // 9. 将actions映射到仓库中,并且如果是结构导入{increment()}调用时 this应该正确指向当前store // 10. 将getters映射到仓库中(将其处理成对象形式), 使用computed包裹并且要保持this,然后可以通过key直接获取到返回内容,无需调用函数 export function defineStore(idOrOptions,setup){ let id = null let options = null if(typeof id === 'string'){ // setup的方式配置 id = idOrOptions options = setup // TODO setup方式待实现 }else if(idOrOptions.id) { // options 方式 options = idOrOptions id = idOrOptions.id } // 处理数据 function useStore(){ // 获取全局pinia getCurrentInstance() app组件实例 const instance = getCurrentInstance() const pinia = instance && inject(piniaSymbol) if(!pinia._s.has(id)){ // 第一次store // 仓库映射 createOptionsStore(id,options,pinia) } const store = pinia._s.get(id) return store } return useStore // 用户最终函数调用可以获取到state } /** * 初始化store * @param {*} id 仓库id * @param {*} options 用户选项:{state,getters,actions} * @param {*} pinia 全局pinia实例 */ function createOptionsStore(id,options,pinia){ const {state,getters,actions} = options const store = reactive({}) // 处理 全局停用,自己停止自己 let scope = null function setup(){ // 如果当前仓库的state不存在 那么赋值空对象,并且将当前的state 保存到全局pinia的state中 const localState = pinia.state.value[id] = state ? state() : {} // getters 可能为空 return Object.assign(localState,actions,Object.keys(getters || {}).reduce((meno,name)=>{ // 处理getters将其映射为响应,并且保持this meno[name] = computed(()=>{ return getters[name].call(store) }) return meno },{})) } // 先用全局scope包裹局部scope,这样就可以实现全局停用,和局部停用 // pinia._e.stop // scope.stop const steupStore = pinia._e.run(()=>{ scope = effectScope() return scope.run(()=>setup()) }) // 处理actions的this指向问题 function warpAction(key,action){ return function(){ let ret = action.apply(store,arguments) // TODO 如果actions执行后是Promise return ret } } for(let key in steupStore){ const prop = steupStore[key] if(typeof prop === 'function'){ // 对action中的this 和 后续逻辑进行处理,函数劫持 steupStore[key] = warpAction(key,prop) } } // 映射全局存储 pinia._s.set(id,store) // 将id于store映射 Object.assign(store,steupStore) return store } ``` ### 第二版本实现setup方式 - 逻辑与初版本createOptions一样,无非就是setup由用户传入函数定于,所以完全可以抽离逻辑复用 ```js import {piniaSymbol} from './rootState' import {getCurrentInstance,inject,reactive,effectScope,computed} from 'vue' export function defineStore(idOrOptions,setup){ let id = null let options = null if(typeof id === 'string'){ // setup的方式配置 id = idOrOptions options = setup // TODO setup方式待实现 }else if(idOrOptions.id) { // options 方式 options = idOrOptions id = idOrOptions.id } // 判断是不是stup方式 const isSteupStore = typeof setup === 'function' // 处理数据 function useStore(){ // 获取全局pinia getCurrentInstance() app组件实例 const instance = getCurrentInstance() const pinia = instance && inject(piniaSymbol) if(!pinia._s.has(id)){ // 第一次store if(isSteupStore){ // 用户自定义函数 createSteupStore(id,setup,pinia) }else{ // 仓库映射 createOptionsStore(id,options,pinia) } } const store = pinia._s.get(id) return store } return useStore // 用户最终函数调用可以获取到state } /** * 初始化store options版本 * @param {*} id 仓库id * @param {*} options 用户选项:{state,getters,actions} * @param {*} pinia 全局pinia实例 */ function createOptionsStore(id,options,pinia){ const {state,getters,actions} = options function setup(){ // 如果当前仓库的state不存在 那么赋值空对象,并且将当前的state 保存到全局pinia的state中 const localState = pinia.state.value[id] = state ? state() : {} // getters 可能为空 return Object.assign(localState,actions,Object.keys(getters || {}).reduce((meno,name)=>{ // 处理getters将其映射为响应,并且保持this meno[name] = computed(()=>{ const store = pinia._s.get(id) return getters[name].call(store) }) return meno },{})) } createSteupStore(id,setup,pinia) } function createSteupStore(id,setup,pinia){ const store = reactive({}) // 处理 全局停用,自己停止自己 let scope = null // 先用全局scope包裹局部scope,这样就可以实现全局停用,和局部停用 // pinia._e.stop // scope.stop const steupStore = pinia._e.run(()=>{ scope = effectScope() return scope.run(()=>setup()) }) // 处理actions的this指向问题 function warpAction(key,action){ return function(){ let ret = action.apply(store,arguments) // TODO 如果actions执行后是Promise return ret } } for(let key in steupStore){ const prop = steupStore[key] if(typeof prop === 'function'){ // 对action中的this 和 后续逻辑进行处理,函数劫持 steupStore[key] = warpAction(key,prop) } } // 映射全局存储 pinia._s.set(id,store) // 将id于store映射 Object.assign(store,steupStore) return store } ``` ## 正确处理setup方式的state - 之前版本遗留问题,我们只对options的state存储了,那么setup的state是用户自定义的,我们并不能区分。 - 根据初始化传递函数,传入是否是optionsApi,并且对于setup而言,初始化时全局pinia中的state并没有存储。满足以上条件,我们赋值空对象,然后再进行详细的判断抽取state。 - 判断逻辑 只要是ref 并且不是computed那么就判断为 ```js import {piniaSymbol} from './rootState' import {getCurrentInstance,inject,reactive,effectScope,computed,isRef,isReactive} from 'vue' // 1. 先处理命名空间如果是{id:''} 或者 字符串 // 2. 返回一个函数,用户拿到后调用可以获取到store // 3. 通过getCurrentInstance 获取app组件实例,然后通过inject然后获取pinia实例 // 4. 通过pinia._s 来获取仓库 如果当前仓库不存在进行初始化 如果存在进行获取返回 // 5. 处理仓库初始化的操作 创建一个createOptions 进行仓库映射 // 6. 对于全局pinia可以直接调用_e.stop停止所有仓库响应时,store可以自己停止自己 // 7. 创建局部仓库映射到全局_s存储中,并返回。通过reactive包裹 // 8. 处理局部仓库内容,然后添加到仓库中 // 9. 将actions映射到仓库中,并且如果是结构导入{increment()}调用时 this应该正确指向当前store // 10. 将getters映射到仓库中(将其处理成对象形式), 使用computed包裹并且要保持this,然后可以通过key直接获取到返回内容,无需调用函数 // 计算属性是一个ref 同时也是一个effect function isComputed(prop){ return !!(isRef(prop) && prop.effect) } export function defineStore(idOrOptions,setup){ let id = null let options = null if(typeof idOrOptions === 'string'){ // setup的方式配置 id = idOrOptions options = setup // TODO setup方式待实现 }else if(idOrOptions.id) { // options 方式 options = idOrOptions id = idOrOptions.id } // 判断是不是stup方式 const isSteupStore = typeof setup === 'function' // 处理数据 function useStore(){ // 获取全局pinia getCurrentInstance() app组件实例 const instance = getCurrentInstance() const pinia = instance && inject(piniaSymbol) if(!pinia._s.has(id)){ // 第一次store if(isSteupStore){ // 用户自定义函数 createSteupStore(id,setup,pinia,isSteupStore) }else{ // 仓库映射 createOptionsStore(id,options,pinia,isSteupStore) } } const store = pinia._s.get(id) console.log(pinia.state.value) return store } return useStore // 用户最终函数调用可以获取到state } /** * 初始化store options版本 * @param {*} id 仓库id * @param {*} options 用户选项:{state,getters,actions} * @param {*} pinia 全局pinia实例 */ function createOptionsStore(id,options,pinia){ const {state,getters,actions} = options function setup(){ // 如果当前仓库的state不存在 那么赋值空对象,并且将当前的state 保存到全局pinia的state中 const localState = pinia.state.value[id] = state ? state() : {} // getters 可能为空 return Object.assign(localState,actions,Object.keys(getters || {}).reduce((meno,name)=>{ // 处理getters将其映射为响应,并且保持this meno[name] = computed(()=>{ const store = pinia._s.get(id) return getters[name].call(store) }) return meno },{})) } createSteupStore(id,setup,pinia) } function createSteupStore(id,setup,pinia,optionsApi){ const store = reactive({}) // 处理 全局停用,自己停止自己 let scope = null // setup 的state为空 const initiaState = pinia.state.value[id] if(!initiaState && optionsApi){ pinia.state.value[id] = {} } // 先用全局scope包裹局部scope,这样就可以实现全局停用,和局部停用 // pinia._e.stop // scope.stop const steupStore = pinia._e.run(()=>{ scope = effectScope() return scope.run(()=>setup()) }) // 处理actions的this指向问题 function warpAction(key,action){ return function(){ let ret = action.apply(store,arguments) // TODO 如果actions执行后是Promise return ret } } for(let key in steupStore){ const prop = steupStore[key] if(typeof prop === 'function'){ // 对action中的this 和 后续逻辑进行处理,函数劫持 steupStore[key] = warpAction(key,prop) } // 看看这个值是不是状态 // computed他也是ref if(isRef(prop) && !isComputed(prop) || isReactive(prop)){ if(optionsApi){ // 是setup pinia.state.value[id][key] = prop } } } // 映射全局存储 pinia._s.set(id,store) // 将id于store映射 Object.assign(store,steupStore) return store } ```