这一定是你看过的最简单的 pinia 源码
Posted X可乐
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了这一定是你看过的最简单的 pinia 源码相关的知识,希望对你有一定的参考价值。
为什么使用pinia?
- pinia 是一种全局状态管理工具,其底层其实是对 Vue 自身状态的一种高度封装,本期我们主要讨论 pinia 配合 Vue3 的使用, 在 vue3 中, pinia 的全局状态也是基于 reactive 达成的一种 代理对象
和 vuex 的区别?
- 相对于 vuex,对 ts 的支持更加友好,具有可靠的类型推断。
- 支持多个 store 协同。
- 去除 mutations,只有 state,getters,actions。
- 无模块嵌套,只有 store,store 之间可以自由使用,更好的代码分割;
基础使用
-
使用vite 构建项目
npm init vite@latest my-vue-app -- --template vue
选择 vue —> vue + ts
-
项目中添加 pinia
npm install pinia
-
目录结构
- src - api - assets - components - Home.vue - store - index.ts
store 为全局仓库定义处,使用的时候 都需要从此处引入,可以多人维护一个,也可以每个人单独维护一个自己的 store,即在自己负责的组件或模块中,同此全局仓库的定义方法和使用方法相同,只在引入的时候使用不同的仓库名即可。
-
挂载 pinia
// main.ts
import createApp from 'vue'
import App from './App.vue'
import createPinia from 'pinia'
const pinia = createPinia()
const app = createApp(App);
app.use(pinia);
app.mount('#app');
- 使用
引入的时候,一共有以下几种方法
// 在使用前需要先实例化
import mainStore from './../store/index'
const store = mainStore();
-
直接解构
- 会失去响应式
- 可以解构 actions 中的方法
-
使用 点语法访问
- 可以配合计算属性做进一步处理
- 可以访问到 getter 和 actions
-
使用 storeToRefs
- 解构以后将其包装为响应式对象,调用时需要变量名后加 .value
- 不能解构 actions 中的方法
-
$path
- 可以使用 此方法直接对 store中的多个数据进行更改,接收一个对象或一个回调函数,回调函数接收一个参数(state),可以分别对状态里的某个集合进行指定更改,而不是从新赋值,使用对象的形式只能从新赋值
store.$patch( counter: store.counter + 1, name: 'Abalam', ) cartStore.$patch((state) => state.items.push( name: 'shoes', quantity: 1 ) state.hasChanged = true )
-
$state
- 用于替换整个状态, 也可以使用 store.state.value = 来替换
store.$state = counter: 666, name: 'Paimon' store.state.value =
-
方法
在 getter 和actions 中 都可以通过 this 访问到当前 pinia中的 其他 getter 和 actions中的函数,实现相互调用
-
pinia中的 getter
- getter 和 Vue 中的计算属性几乎一样,在获取 State 值之前做一些逻辑处理, 会缓存上一次的值,如果没有更新,则只会调用一次
- 使用的时候 getter 和 state 导入的方法是一样的,因为getter 只是将我们在其中定义的变量包装了一层 proxy 打入到 state 中,但 如果 getter 使用了高阶函数,返回了一个函数, 在使用的时候,导入方法同 actions
- 通过以下第二个方式的调用,也可以实现通过传值的形式,对store中的值进行修改,但不建议这样做,修改的逻辑尽量还是放在actions中
// 通过getter 获得计算以后的数据 getters: // 获取计算以后的属性 getData(state):boolean return !this.data.msg , // 通过传值的方式获取计算以后的属性 getCurrentData(state) return (bool:boolean) => return this.data.msg = bool
-
pinia中的 actions
- 支持异步async的使用,通过 this的方式直接访问 state中定义的状态及修改状态
import mainStore from '../../material_show/pinia' actions: setMainStore() //可以直接引入其他仓库的pinia,获取其值或调用getter和actions ++mainStore().stuRefresh; ++mainStore().parRefresh; , // 更改用户登录状态 changeUserStatus(bool:boolean) this.userStatus = bool; , // 更新教师信息 updateTeacher(options:teacherType) this.teacherDetail = ...options; ,
-
源码剖析
- 根据上边的基础使用我们可以知道,pinia 的使用分为 3 步
- 通过 createPinia 在 vue 实例化前挂载到 vue 上
- 通过 defineStore 配置为 store 仓库
- 通过 defineStore 的返回值,获取到 store 中的 state/getters/actions
创建 createPinia
- 函数内部对外暴露一个对象
- 对象内部提供一个 install 方法,该方法会在 app.use(pinia) 时执行,且会传递一个参数为 vue 自身
- install 内部只需要将pinia 通过 provide 暴露出去,以供所有子组件使用
- 对象内部除 install 方法外还有两个属性为 state 与 _s,state 用于存储 store 仓库的数据,_s 用于存储 store,如多人开发时可能会每人维护一个 pinia
const piniaSymbol = Symbol('pinia')
function createPinia()
let pinia =
install(app)
app.provide(piniaSymbol, pinia)
,
state: ref(), // store 中的 state
_s: new Map() // 存储每一个 store
return pinia
创建 defineStore
- 在上文提到,defineStore 接受两个参数,一个字符串,一个配置对象(还有其他配置方法,效果相同本文不做探讨)
- 该方法内部返回一个对象,对象在组件中调用后可以通过点语法调用state/getters/actions
function defineStore(id, options)
function useStore() ...
return useStore
创建 useStore
- 通过 defineStore 我们可以知道该方法内部在调用成功后,可以通过点语法获取到 state/getters/actions
- 所以该方法内部会先注入 pinia
function useStore()
let pinia = inject(piniaSymbol) // piniaSymbol 为在 createPina 代码段内声明的全局变量
if (!pinia._s.get(id)) // 当 pinia._s.get(id) 获取不到值时在判断内部设置上
// 将 store 存储到 pinia._s 内,并合并state/getters/actions 到一个对象中
createOtionsStore(id, options, pinia)
let store = pinia._s.get(id) // 获取到处理后 store 将其 return
return store
创建 createOptionsStore
- 通过 useStore 方法我们可以确定 createOptionsStore 的功能有两个
- 将 state/getters/actions 合并到一个对象上
- 将该对象存储到 pinia._s 中
function createOtionsStore(id, options, pinia)
let store = reactive(
_p: pinia,
id
) // 创建 store
Object.assign(store, setup()) // 将 setup 的返回值通过 Object.assign 合并到 store 对象中
pinia._s.set(id, store) // 将 处理后的 store 存储到 pinia._s 中
function setup() ......
创建 setup
- 看完 createOptionsStore 的方法相比你也肯定可以联想到 setup 就是将 state/getters/actions 合并到一个对象中
- 此处 actions 源码过多,所以没有处理
function setup()
// 将 需要用到的 state/getters/actions 从 options 中解构出来
const state, getters, actions = options
// 判断 pinia.state.value 中是否有值,如果没值则将 state 的调用结果赋值进去
!pinia.state.value[id] && (pinia.state.value[id] = state())
// 此时通过 toRefs 将 pinia.state.value[id] 处理为响应式的,暂存与 localState
let localState = toRefs(pinia.state.value[id])
// 处理getters 时先拿到内部所有的 key 组成的数组,并遍历给其包装为 computed
let localGetters = Object.keys(getters).reduce((item, name) =>
item[name] = computed(() =>
const store = pinia._s.get(id)
// 通过 call 将 getters 内部方法的 this 指向了store,并给其传递了唯一的参数 也就是 store 自身
return getters[name].call(store, store)
)
return item
, )
// 此处将 处理后的 state/getters/actions 返回
return Object.assign(localState, localGetters, actions)
总结
- 至此我们手写的简易版 pinia 已完成,该案例只实现了一些基础的功能,其他还有很多功能没有完善,如 actions 的处理,vue2/vue3 的处理,pinia 插件的处理等,后续有时间在更,对该案例有看不懂的地方可以评论区交流
以上是关于这一定是你看过的最简单的 pinia 源码的主要内容,如果未能解决你的问题,请参考以下文章