一个 vue3 compositionApi readonly 在 pinia中使用 引发的惨案!!!!

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的逻辑进行大概梳理。

  1. defineStore 在注册时,当拿到 store 的setup数据时, 会对其进行 类型推断, 接受两个类型:function/object
  2. function (后面称为setup方式)会 调用 createSetupStore 来初始化 store数据;object(后面称为 options) 会调用 createOptionsStore 来初始化数据

setup 方式

  1. 在全局的pinia 实例身上创建 一个 以id 为 键的空对象,方便以后赋值
  2. 使用reactive声明一个响应式对象store
  3. 将store存至pinia._s中
  4. 执行setup 获取 对象的返回值 setupStore
  5. 遍历setupStore的键值,如果值是ref(不是computed)或reactive,将键值添加到pinia.state.value[$id]中;如果值时function,首先将值使用wrapAction包装,然后用包装后的function替换setupStore中对应的值
  6. 将setupStore合并到store中
  7. 拦截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更新
  8. 调用pinia._p中的扩展函数,扩展store

options 方式

  1. 从options中提取state、getter、actions
  2. 构建setup函数,在setup函数中会将getter处理成计算属性
  3. 使用setup方式创建store
  4. 重写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 的响应式 逻辑了。 相信到时候,我对这一块会有更深层次的 理解!!

如果大家觉得我的这篇文章有任何问题,欢迎批评。有更好的思路也可以提出来,我们一起思考一下。欢迎来盘

相关推荐
wycode26 分钟前
Vue2实践(2)之用component做一个动态表单(一)
前端·javascript·vue.js
第七种黄昏28 分钟前
Vue3 中的 ref、模板引用和 defineExpose 详解
前端·javascript·vue.js
pepedd8642 小时前
还在开发vue2老项目吗?本文带你梳理vue版本区别
前端·vue.js·trae
前端缘梦2 小时前
深入理解 Vue 中的虚拟 DOM:原理与实战价值
前端·vue.js·面试
HWL56792 小时前
pnpm(Performant npm)的安装
前端·vue.js·npm·node.js
柯南95273 小时前
Vue 3 reactive.ts 源码理解
vue.js
柯南95273 小时前
Vue 3 Ref 源码解析
vue.js
小高0073 小时前
面试官:npm run build 到底干了什么?从 package.json 到 dist 的 7 步拆解
前端·javascript·vue.js
JayceM4 小时前
Vue中v-show与v-if的区别
前端·javascript·vue.js
HWL56794 小时前
“preinstall“: “npx only-allow pnpm“
运维·服务器·前端·javascript·vue.js