pinia中如果想要给一个state中的数据修改成 只读模式, 直接使用 vue3 的 readonly方法修改后,赋值是不会生效的, 需要给readonly 修改的数据 解构后再 赋值才可以
css
import {readonly} from 'vue'
........
state: {
readonlyData: {}
}
actions: {
setReadonlyData: () => {
this.readonlyData = readonly({
a: '1',
b: 'b'
c: 'c'
}) // 这样是赋值失败的
this.readonlyData = {
...readonly({
a: '1',
b: 'b'
c: 'c'
})
}
}
}
我问自己了一个问题,为啥不能这么用呢, 不是 说 pinia 是使用的 vue3的响应式原理吗?
开始思考!!!
尝试开启 一号哈哈 与二号haha 对话模式 一号哈哈: 原因显而易见呀,pinia中对 readonly 这个预防进行了包装,导致不可更新。 二号haha: 是的,可是这中做法在 vue setup 语法中支持吗?尝试一下吧~
测试思路: pinia中对store 是一个由 proxy 包裹的对象, 所以我们需要创建一个 reactive对象,作为store,并在store上绑定一个userData的reactive默认值。 接着把数据更新成 readonly 对象, 发现数据可以更新成功,切尝试修改数据是失败的
javascript
// 生成store
let testStore = reactive({
userData: {},
})
// 打印store数据
console.log('reactive',testStore)
// 这一步在pinia中是失效的
// 修改readonly数据为 只读数据
testStore.userData = readonly(data);
console.log('readonly1',testStore.userData)
// 尝试修改 readonly数据
testStore.userData.name = 'zh哈哈'
执行结果如下
一号哈哈: 现在可以确认在 vue setup 中 是支持这种数据处理方式的。那就可以确认是 pinia 做了处理了。 好了, 到此结束。不重要! 二号haha: 可是我还是很好奇, 到底是哪一行代码影响到的呢?要不看看源码吧~ 打开github 搜索 pinia 找到 源码。说实话, 有点 难以看懂。。然后在技术网站上看有没有大佬写的源码解析的 文章。很巧,还真有。 文章里把大概的逻辑讲清楚了, 这里放一个链接。需要看细节的,可以去看原文。
由于我猜测 是在store注册的时候,对数据进行了处理, 所以这里针对defineStore的逻辑进行大概梳理。
- defineStore 在注册时,当拿到 store 的setup数据时, 会对其进行 类型推断, 接受两个类型:function/object
- function (后面称为setup方式)会 调用 createSetupStore 来初始化 store数据;object(后面称为 options) 会调用 createOptionsStore 来初始化数据
setup 方式
- 在全局的pinia 实例身上创建 一个 以id 为 键的空对象,方便以后赋值
- 使用reactive声明一个响应式对象store
- 将store存至pinia._s中
- 执行setup 获取 对象的返回值 setupStore
- 遍历setupStore的键值,如果值是ref(不是computed)或reactive,将键值添加到pinia.state.value[$id]中;如果值时function,首先将值使用wrapAction包装,然后用包装后的function替换setupStore中对应的值
- 将setupStore合并到store中
- 拦截store. <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a t e ,使 g e t 操作可以正确获取 p i n i a . s t a t e . v a l u e [ state,使get操作可以正确获取pinia.state.value[ </math>state,使get操作可以正确获取pinia.state.value[id],set操作使用this.$patch更新
- 调用pinia._p中的扩展函数,扩展store
options 方式
- 从options中提取state、getter、actions
- 构建setup函数,在setup函数中会将getter处理成计算属性
- 使用setup方式创建store
- 重写store.$reset
结合源码的大概逻辑及自己的需要验证的问题 不难看出:我们之前推测的 pinia 的 额外处理,应该是在 setup 方式 的第七步,this. <math xmlns="http://www.w3.org/1998/Math/MathML"> p a t c h 中做的然后我仔细看了一下 patch中做的 然后我仔细看了一下 </math>patch中做的然后我仔细看了一下patch的处理逻辑,也在实际的代码中的 <math xmlns="http://www.w3.org/1998/Math/MathML"> p a t c h 加了 d e b u g g e r ,发现其实并不会出发 patch 加了 debugger, 发现其实并不会出发 </math>patch加了debugger,发现其实并不会出发patch!!!
我懵了,到这个时候, 我其实想放弃了。因为,读源码什么的真的很辛苦! 内心OS: 一号哈哈😈: 结束吧!毁灭吧,只要知道结果不久好了。 打把游戏不香吗,干嘛这样对自己 二号haha👼: 不行呀,不行呀,感觉自己离真相很近了。这个时候放弃,你刚给自己定的学习计划,不就又执行不下去了!在想想 一号哈哈😈: 固执!
再努力一下!如果 不是第七步, 那就是 第5步的 wrapAction 方法在包装是做了操作
最终结论 之前在使用this.语法更新 store内的数据为readonly时 因为赋值的方式没有触发 是因为reactive 的引用地址直接就变了, 使用 $patch方法就可以解决
打印发现 在action中的this 与 $patch 的callback中 拿到的 state 不是同一个数据
但是无论哪种方式,在赋值成 readOnly 的时候, 数据的 set函数就都不会继续执行了, 唯一的区别就是 this赋值连 vue devtools工具也监听不到,但是使用 $patch更新数据时,vue devtools 是可以监听到的。
其实在使用this.语法更新 store内的数据时,会把通过 defienStore 创建的数据中的对象直接给更新掉,导致pinia的引用也会被更新。 但是在使用$patch时,更新的是这个sotre的原数据, 所以pinia是可以监听到的。
那么 this 与 <math xmlns="http://www.w3.org/1998/Math/MathML"> p a t c h 中的回调都是哪里来的数据呢?? t h i s 的指向就是我们在组件中调用 d e f i n e S t o r e 返回的方法获取到的 s t o r e 即 p i n i a . s 这个 M a p 对象中的。 patch中的回调都是哪里来的数据呢?? this 的指向就是 我们在组件中 调用 defineStore 返回的方法 获取到的 store 即 pinia._s这个Map对象中的 。 </math>patch中的回调都是哪里来的数据呢??this的指向就是我们在组件中调用defineStore返回的方法获取到的store即pinia.s这个Map对象中的。patch 的数据 就是我们之前放进去的 pinia.state.value[$id]
至于页面没有执行数据的副作用函数, 是因为 readonly 的 逻辑其实就是把一个数据的 set监听给忽略, 代码大概逻辑如下, 具体的可以去看一下 vue源码中关于响应式系统中的代码
javascript
const readonly = function (value) {
return new Proxy(value, {
get: (target, key) => {
return target[key]
},
set: () => {
console.warn(`${key.toString()} 不能被set, 因为是readonly, ${target}`)
return true
}
})
}
额外收获
- pinia支持 setup 方式声明 store, 之前看文档的时候,忽略了。 下面贴出来我用 setup 语法创建store 及使用
- readonly 的源码逻辑
- pinia 的 源码逻辑
其实上面这个问题,如果使用 setup 语法会更加的清晰明了。
终于把问题的真正原因盘通了,
其实真正在排查这个问题的时候,还走了很多的 弯路, 但是觉得没有必要的就没有放出来。总结就是一句话,当你想不通问题时, 及时休息一下。别让自己在一个思维里跳不出来,毕竟大脑也需要休息 到此,第一次的 pinia 路行 大概可以结束了,还有一些问题还没有真正搞明白。比如:this指向的数据和 devtools 是怎么联动??pinia_s这个map中的数据与 vue 有什么联系?? 但是,我觉得现阶段,继续深究,没有任何的意义了。先放一放。我要去好好的过一下 vue3 的响应式 逻辑了。 相信到时候,我对这一块会有更深层次的 理解!!
如果大家觉得我的这篇文章有任何问题,欢迎批评。有更好的思路也可以提出来,我们一起思考一下。欢迎来盘