Vue3中响应式数据深度拷贝 Avoid app logic that relies on,,,,,,,,

最近,在项目中遇到了一个问题:当在线上环境的时候提示

但是在开发环境中没找到该问题,但是发现会提示这样的警告。

Avoid app logic that relies on enumerating keys on a component instance. The keys will be empty in production mode to avoid performance overhead.

中文翻译了下是:

避免编写依赖于枚举组件实例键的应用逻辑。在生产模式下,这些键将为空,以减少性能开销

检索了下问题是大概意思是:禁止对Vue组件实例进行循环,由于Vue内置了一些内部属性和方法

定位了下问题大致是在:使用loadshcloneDeep方法去对pinia中的this.$state数据进行深拷贝的时候

这个项目的大概需求是: 想要将pinia中的state中的数据提交给后端,但state中的有些数据不需要提交,需要对其进行delete操作,同时为了在提交的数据的时候也不影响页面的展示情况,只能先对state进行深拷贝出一个全新的数据,然后对深拷贝出来的数据进行数据处理之后提交。大致和下面这段代码相同。

javascript 复制代码
import { defineStore } from 'pinia'
import _ from 'lodash' // 需安装lodash:npm i lodash

export const useMyStore = defineStore('myStore', {
  state: () => ({
    // 任意类型的状态
    obj: { x: 1, y: { z: 2 } },
    arr: [1, [2, 3]],
    func: () => console.log('test') // 注意:lodash会拷贝函数引用
  }),
  actions: {
    saveSiteDatas() {
      const siteData = getSiteData(_.cloneDeep(this.$state)) // 获取过滤后的拷贝数据
      postData(siteData, () => {
          .....
      });
    },
  }
})

根据上面的警告⚠️提示,知道是loadsh在对this.$state数据进行深拷贝的时候对Vue的组件实例内置数据进行了循环。

既然是深拷贝的时候出了问题,那就替换深拷贝的方式。

最直接的方式就是 通过使用JSON序列化的方式:但是发现使用JSON实现深拷贝的时候会报红,发现这个方式不能使用。

javascript 复制代码
const siteData = JSON.parse(JSON.stringfy(this.%state))

但是发现在替换成这样之后,提示标红啦。

根据上面查询到的资料来看,这个是JSON序列化的时候发现state对象中有Vue的组件实例化对象,这个对象中有属性是引用到自身的。例如在实例对象下的this._self是指向实例对象本身,那么就出现了循环引用。

在查询的过程中发现是数据的响应式导致了无法深度复制。那根据上面的说法是可以将响应式对象转化成普通JS对象来对数据进行深拷贝

那么现在需要做的是先将state上的每个数据都进行响应式去除,然后进行深度拷贝

javascript 复制代码
deepCloneNonReactive(source: any): any {
      // 使用 WeakMap 来缓存已处理的对象,避免循环引用导致的无限递归
      const cache = new WeakMap();
      
      const clone = (item: any): any => {
        // 1. 处理基本类型和 null:直接返回
        if (typeof item !== 'object' || item === null) {
          return item;
        }

        // 2. 检查缓存:如果已经处理过,直接返回缓存的结果
        if (cache.has(item)) {
          return cache.get(item);
        }

        // 3. 处理 ref 类型:先解包获取原始值
        if (isRef(item)) {
          const unrefValue = unref(item);
          return clone(unrefValue);
        }

        // 4. 处理 reactive 类型:转为原始对象
        if (isReactive(item)) {
          const rawValue = toRaw(item);
          return clone(rawValue);
        }

        // 5. 处理 Date 对象:创建新的 Date 实例
        if (item instanceof Date) {
          const newDate = new Date(item.getTime());
          cache.set(item, newDate);
          return newDate;
        }

        // 6. 处理 RegExp 对象:创建新的 RegExp 实例
        if (item instanceof RegExp) {
          const newRegExp = new RegExp(item.source, item.flags);
          cache.set(item, newRegExp);
          return newRegExp;
        }

        // 7. 处理 Map 对象:创建新的 Map 实例
        if (item instanceof Map) {
          const newMap = new Map();
          cache.set(item, newMap);
          item.forEach((value, key) => {
            newMap.set(clone(key), clone(value));
          });
          return newMap;
        }

        // 8. 处理 Set 对象:创建新的 Set 实例
        if (item instanceof Set) {
          const newSet = new Set();
          cache.set(item, newSet);
          item.forEach(value => {
            newSet.add(clone(value));
          });
          return newSet;
        }

        // 9. 处理数组:创建新数组,递归处理每个元素
        if (Array.isArray(item)) {
          const newArray: any[] = [];
          cache.set(item, newArray);
          for (let i = 0; i < item.length; i++) {
            newArray[i] = clone(item[i]);
          }
          return newArray;
        }

        // 10. 处理普通对象:创建新对象,递归处理每个属性
        if (Object.prototype.toString.call(item) === '[object Object]') {
          // 检查是否是 Vue 组件实例,避免在生产模式下枚举组件实例属性
          if (item.$ && typeof item.$ === 'object' && item._ && typeof item._ === 'object') {
            // 如果是 Vue 组件实例,直接返回原始值,避免枚举其属性
            console.log(item)
            return item;
          }
          
          const newObject: Record<string | symbol, any> = {};
          cache.set(item, newObject);
          
          // 处理字符串属性
          for (const key of Object.keys(item)) {
            newObject[key] = clone(item[key]);
          }
          
          // 处理 Symbol 属性
          const symbolKeys = Object.getOwnPropertySymbols(item);
          for (const symKey of symbolKeys) {
            newObject[symKey] = clone(item[symKey]);
          }
          
          return newObject;
        }

        // 11. 其他未处理的类型:直接返回原始值
        return item;
      };

      return clone(source);
    },

其中核心就是对reactive 和 ref对象进行深度解构,解构出原始对象进行复制,对对象下的属性也同样进行相同的操作,其中这块代码是主要的去除掉黄色警告⚠️的核心。

如果一个对象是Vue组件实例,那么该对象会包含item._item.$这样的内置属性,所以可以用其来判断是否是个Vue实例组件,对于实例组件就不进行向下的循环,避免出现无限递归的情况,同时也WeakMap去避免无限递归的情况。

reactive对象和ref解构原始对象可以使用Vue3提供的toRawtoRef处理

Map和WeakMap: juejin.cn/post/743975...

相关推荐
六六Leon3 小时前
Kuikly跨端模式接入资源管理
前端
tianchang3 小时前
深入理解 JavaScript 异步机制:从语言语义到事件循环的全景图
前端·javascript
旺仔牛仔QQ糖3 小时前
Vue3.0 Hook 使用好用多多
前端
~无忧花开~3 小时前
CSS学习笔记(五):CSS媒体查询入门指南
开发语言·前端·css·学习·媒体
程序猿小D3 小时前
【完整源码+数据集+部署教程】【零售和消费品&存货】价格标签检测系统源码&数据集全套:改进yolo11-RFAConv
前端·yolo·计算机视觉·目标跟踪·数据集·yolo11·价格标签检测系统源码
吴鹰飞侠4 小时前
AJAX的学习
前端·学习·ajax
JNU freshman4 小时前
vue 技巧与易错
前端·javascript·vue.js
落一落,掉一掉4 小时前
第十二周 waf绕过和前端加密绕过
前端
Asort4 小时前
JavaScript设计模式(十六)——迭代器模式:优雅遍历数据的艺术
前端·javascript·设计模式