一个 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 的响应式 逻辑了。 相信到时候,我对这一块会有更深层次的 理解!!

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

相关推荐
爱喝水的小鼠1 小时前
Vue3(一) Vite创建Vue3工程,选项式API与组合式API;setup的使用;Vue中的响应式ref,reactive
前端·javascript·vue.js
小晗同学1 小时前
Vue 实现高级穿梭框 Transfer 封装
javascript·vue.js·elementui
forwardMyLife1 小时前
element-plus的面包屑组件el-breadcrumb
javascript·vue.js·ecmascript
计算机学姐2 小时前
基于python+django+vue的影视推荐系统
开发语言·vue.js·后端·python·mysql·django·intellij-idea
luoluoal2 小时前
java项目之基于Spring Boot智能无人仓库管理源码(springboot+vue)
java·vue.js·spring boot
mez_Blog3 小时前
个人小结(2.0)
前端·javascript·vue.js·学习·typescript
深情废杨杨3 小时前
前端vue-插值表达式和v-html的区别
前端·javascript·vue.js
GHUIJS3 小时前
【vue3】vue3.3新特性真香
前端·javascript·vue.js
众生回避3 小时前
鸿蒙ms参考
前端·javascript·vue.js
洛千陨3 小时前
Vue + element-ui实现动态表单项以及动态校验规则
前端·vue.js