news 2026/5/14 8:07:36

vue3对象复制/拷贝/克隆

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
vue3对象复制/拷贝/克隆

应用示例代码:

import { cloneDeep } from "lodash-es"; // 空资金信息 const emptyCapitalInfo: CapitalInfo = { id: 0, capitalNo: "", capitalName: "", capitalType: "", capitalTotal: 0, capitalLeaveTotal: 0, capitalValidTotal: 0, capitalIndexType: "", capitalAccount: "", capitalSource: "", capitalIndexSource: "", capitalYear: new Date().getFullYear(), capitalState: 0, remark: "", createTime: "", filePathname: "", isNullContent: true, fileUrls: [] }; // 创建本地响应式副本 const localCapitalInfo = reactive<CapitalInfo>({ ...emptyCapitalInfo }); // 新增 const onAddClick = () => { // capitalInfoStore.setCurrentSelectedCapitalInfo(emptyCapitalInfo); // ❌浅拷贝:直接引用源对象,修改源数据可能影响目标,修改localCapitalInfo会影响sourceData,从而影响capitalInfoStore.currentSelectedCapitalInfo,从而影响emptyCapitalInfo // capitalInfoStore.setCurrentSelectedCapitalInfo({ ...emptyCapitalInfo }); // ✅浅拷贝:创建新对象,与源对象解耦,与源数据隔离,更安全,修改localCapitalInfo不会影响sourceData、capitalInfoStore.currentSelectedCapitalInfo和emptyCapitalInfo // capitalInfoStore.setCurrentSelectedCapitalInfo(structuredClone(emptyCapitalInfo)); // ✅深拷贝:现代浏览器原生方法,现代浏览器原生方法structuredClone无法克隆某些特殊类型的对象(响应式代理对象(reactive/proxy)、函数或方法、Symbol 类型的属性、循环引用、特殊的 DOM 对象),因为emptyCapitalInfo是原始对角不是响应式对象,所以不需要toRaw() // capitalInfoStore.setCurrentSelectedCapitalInfo(JSON.parse(JSON.stringify(emptyCapitalInfo))); // ✅深拷贝(推荐): capitalInfoStore.setCurrentSelectedCapitalInfo(cloneDeep(emptyCapitalInfo)); // ✅深拷贝(专业):使用第三方库 lodash/lodash-es 的 cloneDeep,功能全面、可靠、保留更多类型 // 清空附件 file.value = null; infoDialogVisible.value = true; }; // 监视信息对话框状态,同步数据 watch(infoDialogVisible, (newValue) => { if (newValue) { // 对话框打开时,从store复制数据到本地副本 const sourceData = capitalInfoStore.currentSelectedCapitalInfo || emptyCapitalInfo; // Object.assign(localCapitalInfo, sourceData); // ❌浅拷贝:直接引用源对象,修改源数据可能影响目标,修改localCapitalInfo会影响sourceData,从而影响emptyCapitalInfo // Object.assign(localCapitalInfo, { ...sourceData }); // ✅浅拷贝:创建新对象,与源对象解耦,与源数据隔离,更安全,修改localCapitalInfo不会影响sourceData和emptyCapitalInfo // Object.assign(localCapitalInfo, structuredClone(toRaw(sourceData))); // ✅深拷贝:现代浏览器原生方法structuredClone无法克隆某些特殊类型的对象(响应式代理对象(reactive/proxy)、函数或方法、Symbol 类型的属性、循环引用、特殊的 DOM 对象),因为sourceData有可以是响应式对象,所以需要使用 toRaw() 剥离响应式代理,获取原始对象 // Object.assign(localCapitalInfo, JSON.parse(JSON.stringify(sourceData))); // ✅深拷贝(推荐):因为localCapitalInfo为reactive对象,不能直接赋值,如果是ref对象,可以 localCapitalInfo.value = JSON.parse(JSON.stringify(sourceData)); Object.assign(localCapitalInfo, cloneDeep(sourceData)); // ✅深拷贝(专业):使用第三方库 lodash/lodash-es 的 cloneDeep,功能全面、可靠、保留更多类型 // 文件选择器实例清空资源,以便可以继续选择文件,不然会提示文件限制 baseFileSelectorRef.value?.cleanupResources(); } });

在 Vue3 中,将sourceData复制到响应式对象localCapitalInfo有以下几种方法:

1.最推荐的方法:使用 Object.assign 配合展开运算符

typescript

// 方法1:使用 Object.assign 和展开运算符 Object.assign(localCapitalInfo, { ...sourceData });

推荐原因:

  • 创建新对象,与源数据解耦

  • Vue3 能更好地处理响应式转换

  • 避免直接引用可能存在的问题

2.直接使用 Object.assign

typescript

// 方法2:直接 Object.assign Object.assign(localCapitalInfo, sourceData);

注意:如果sourceData是响应式代理对象(reactive),这种方式可能会复制代理引用。

3.如果需要确保深拷贝(推荐用于重要数据)

typescript

// 方法3:深拷贝(避免引用问题) import { cloneDeep } from 'lodash-es'; // 或使用 JSON(但注意会丢失函数、Date 等) const copyData = cloneDeep(sourceData); Object.assign(localCapitalInfo, copyData);

4.使用 Vue3 的响应式转换方法

typescript

import { reactive, toRaw } from 'vue'; // 方法4:确保使用原始数据 Object.assign(localCapitalInfo, toRaw(sourceData)); // 或者 Object.assign(localCapitalInfo, { ...toRaw(sourceData) });

5.完整示例代码

vue

<script setup lang="ts"> import { reactive, watch } from 'vue'; import type { CapitalInfo } from '@/types/capital'; // 空资金信息 const emptyCapitalInfo: CapitalInfo = { id: 0, capitalNo: "", capitalName: "", capitalType: "", capitalTotal: 0, capitalLeaveTotal: 0, capitalValidTotal: 0, capitalIndexType: "", capitalAccount: "", capitalSource: "", capitalIndexSource: "", capitalYear: new Date().getFullYear(), capitalState: 0, remark: "", createTime: "", filePathname: "", isNullContent: true, fileUrls: [] }; // 创建本地响应式副本 const localCapitalInfo = reactive<CapitalInfo>({ ...emptyCapitalInfo }); // 对话框打开时,从store复制数据到本地副本 const sourceData = capitalInfoStore.currentSelectedCapitalInfo || emptyCapitalInfo; // 方法1:推荐方式(解耦复制) const copyDataToLocal = () => { // 重置到空状态 Object.assign(localCapitalInfo, { ...emptyCapitalInfo }); // 复制新数据(使用展开运算符创建新对象) if (sourceData) { Object.assign(localCapitalInfo, { ...sourceData }); } }; // 方法2:深拷贝方式(避免引用问题) const copyDataToLocalDeep = () => { // 使用 JSON 方法(简单数据类型适用) const copyData = JSON.parse(JSON.stringify(sourceData || emptyCapitalInfo)); Object.assign(localCapitalInfo, copyData); }; // 方法3:如果需要处理可能存在的响应式代理 const copyDataToLocalSafe = () => { // 先重置 Object.assign(localCapitalInfo, { ...emptyCapitalInfo }); // 复制数据,确保使用原始值 const rawSourceData = sourceData ? JSON.parse(JSON.stringify(sourceData)) : { ...emptyCapitalInfo }; Object.assign(localCapitalInfo, rawSourceData); }; // 监听数据变化 watch(() => localCapitalInfo, (newVal) => { console.log('localCapitalInfo 已更新:', newVal); }, { deep: true }); </script> <template> <div> <!-- 对话框或其他UI组件 --> <button @click="copyDataToLocal">加载数据</button> </div> </template>

6.最佳实践建议

根据你的场景,我推荐以下组合方式:

typescript

// 在对话框打开的函数中 const openDialog = (capitalData?: CapitalInfo) => { // 1. 重置为初始状态 Object.assign(localCapitalInfo, { ...emptyCapitalInfo }); // 2. 如果有传入数据,则复制(使用深拷贝避免引用问题) if (capitalData) { // 使用 JSON 深拷贝(适用于你的数据类型) const copyData = JSON.parse(JSON.stringify(capitalData)); Object.assign(localCapitalInfo, copyData); } else if (capitalInfoStore.currentSelectedCapitalInfo) { // 或者从 store 获取 const copyData = JSON.parse( JSON.stringify(capitalInfoStore.currentSelectedCapitalInfo) ); Object.assign(localCapitalInfo, copyData); } // 3. 显示对话框 dialogVisible.value = true; };

7.针对你的具体需求的优化版本

typescript

import { reactive, toRaw } from 'vue'; // 封装一个专门的数据复制函数 const copyCapitalInfo = (target: CapitalInfo, source: CapitalInfo) => { // 创建一个新对象,确保与源数据解耦 const sourceCopy = { ...source }; // 手动复制每个字段,确保类型正确 target.id = sourceCopy.id; target.capitalNo = sourceCopy.capitalNo; target.capitalName = sourceCopy.capitalName; target.capitalType = sourceCopy.capitalType; target.capitalTotal = sourceCopy.capitalTotal; target.capitalLeaveTotal = sourceCopy.capitalLeaveTotal; target.capitalValidTotal = sourceCopy.capitalValidTotal; target.capitalIndexType = sourceCopy.capitalIndexType; target.capitalAccount = sourceCopy.capitalAccount; target.capitalSource = sourceCopy.capitalSource; target.capitalIndexSource = sourceCopy.capitalIndexSource; target.capitalYear = sourceCopy.capitalYear; target.capitalState = sourceCopy.capitalState; target.remark = sourceCopy.remark; target.createTime = sourceCopy.createTime; target.filePathname = sourceCopy.filePathname; target.isNullContent = sourceCopy.isNullContent; target.fileUrls = [...(sourceCopy.fileUrls || [])]; // 数组浅拷贝 }; // 使用示例 const loadDataToForm = () => { const sourceData = capitalInfoStore.currentSelectedCapitalInfo || emptyCapitalInfo; copyCapitalInfo(localCapitalInfo, sourceData); };

总结建议

对于你的场景,我推荐:

  1. 使用Object.assign(localCapitalInfo, { ...sourceData })- 最简洁安全

  2. 如果需要完全独立的数据:使用 JSON 深拷贝

  3. 始终在复制前考虑重置:先重置到初始状态再复制新数据

typescript

// 最终推荐代码 const loadCapitalData = () => { // 重置 Object.assign(localCapitalInfo, { ...emptyCapitalInfo }); // 复制新数据 if (sourceData) { Object.assign(localCapitalInfo, { ...sourceData }); } };

这种方法在大多数 Vue3 应用中都能很好地工作,既保持了响应式,又避免了不必要的引用问题。


// 监视信息对话框状态,同步数据 watch(infoDialogVisible, (newValue) => { if (newValue) { // 对话框打开时,从store复制数据到本地副本 const sourceData = capitalInfoStore.currentSelectedCapitalInfo || emptyCapitalInfo; // Object.assign(localCapitalInfo, sourceData); // ❌浅拷贝:直接引用源对象,修改源数据可能影响目标,修改localCapitalInfo会影响sourceData,从而影响emptyCapitalInfo // Object.assign(localCapitalInfo, { ...sourceData }); // ✅浅拷贝:创建新对象,与源对象解耦,与源数据隔离,更安全,修改localCapitalInfo不会影响sourceData和emptyCapitalInfo // Object.assign(localCapitalInfo, JSON.parse(JSON.stringify(sourceData))); // ✅深拷贝:因为localCapitalInfo为reactive对象,不能直接赋值,如果是ref对象,可以 localCapitalInfo.value = JSON.parse(JSON.stringify(sourceData)); Object.assign(localCapitalInfo, structuredClone(sourceData)); // ✅深拷贝:现代浏览器原生方法 // 文件选择器实例清空资源,以便可以继续选择文件,不然会提示文件限制 baseFileSelectorRef.value?.cleanupResources(); } }); 报错:CapitalInfo.vue:415 Uncaught (in promise) DataCloneError: Failed to execute 'structuredClone' on 'Window': #<Object> could not be cloned. at CapitalInfo.vue:415:1

structuredClone无法克隆某些特殊类型的对象。在你的场景中,sourceData很可能包含:

  1. Vue3 的响应式代理对象(reactive/proxy)

  2. 函数或方法

  3. Symbol 类型的属性

  4. 循环引用

  5. 特殊的 DOM 对象

解决方案

方案1:使用JSON.parse(JSON.stringify())(推荐)

typescript

watch(infoDialogVisible, (newValue) => { if (newValue) { const sourceData = capitalInfoStore.currentSelectedCapitalInfo || emptyCapitalInfo; // ✅ 使用 JSON 方法进行深拷贝 Object.assign(localCapitalInfo, JSON.parse(JSON.stringify(sourceData))); baseFileSelectorRef.value?.cleanupResources(); } });

方案2:使用toRaw()剥离响应式代理

typescript

import { toRaw } from 'vue'; watch(infoDialogVisible, (newValue) => { if (newValue) { const sourceData = capitalInfoStore.currentSelectedCapitalInfo || emptyCapitalInfo; // ✅ 先剥离响应式代理,再使用 structuredClone const rawData = sourceData === emptyCapitalInfo ? sourceData : toRaw(sourceData); Object.assign(localCapitalInfo, structuredClone(rawData)); baseFileSelectorRef.value?.cleanupResources(); } });

方案3:使用lodash.cloneDeep

typescript

import { cloneDeep } from 'lodash-es'; watch(infoDialogVisible, (newValue) => { if (newValue) { const sourceData = capitalInfoStore.currentSelectedCapitalInfo || emptyCapitalInfo; // ✅ 使用 lodash 的深拷贝 Object.assign(localCapitalInfo, cloneDeep(sourceData)); baseFileSelectorRef.value?.cleanupResources(); } });

方案4:安全的structuredClone封装

typescript

function safeStructuredClone(obj: any): any { try { // 尝试使用 structuredClone return structuredClone(obj); } catch (error) { console.warn('structuredClone failed, falling back to JSON:', error); // 降级到 JSON 方法 return JSON.parse(JSON.stringify(obj)); } } watch(infoDialogVisible, (newValue) => { if (newValue) { const sourceData = capitalInfoStore.currentSelectedCapitalInfo || emptyCapitalInfo; // ✅ 使用安全的深拷贝函数 Object.assign(localCapitalInfo, safeStructuredClone(sourceData)); baseFileSelectorRef.value?.cleanupResources(); } });

方案5:处理函数和特殊类型的深拷贝

typescript

function deepCloneForVue(obj: any): any { // 处理基本类型 if (obj === null || typeof obj !== 'object') { return obj; } // 处理数组 if (Array.isArray(obj)) { return obj.map(item => deepCloneForVue(item)); } // 处理普通对象(剥离 Vue 的响应式代理) const rawObj = toRaw(obj); const cloned = {}; for (const key in rawObj) { // 跳过 Vue 的内部属性 if (key.startsWith('__v_')) continue; const value = rawObj[key]; // 处理函数 if (typeof value === 'function') { // 可以保留函数引用,或者创建一个新的绑定 cloned[key] = value.bind(cloned); } // 处理 Date else if (value instanceof Date) { cloned[key] = new Date(value.getTime()); } // 递归处理对象 else if (value && typeof value === 'object') { cloned[key] = deepCloneForVue(value); } // 基本类型 else { cloned[key] = value; } } return cloned; } watch(infoDialogVisible, (newValue) => { if (newValue) { const sourceData = capitalInfoStore.currentSelectedCapitalInfo || emptyCapitalInfo; // ✅ 使用自定义的 Vue 友好深拷贝 Object.assign(localCapitalInfo, deepCloneForVue(sourceData)); baseFileSelectorRef.value?.cleanupResources(); } });

针对你代码的最佳实践

考虑到你的数据类型CapitalInfo主要是基本类型和数组,推荐使用方案1方案3

推荐方案1:JSON 方法(最简单)

typescript

watch(infoDialogVisible, (newValue) => { if (newValue) { const sourceData = capitalInfoStore.currentSelectedCapitalInfo || emptyCapitalInfo; // ✅ 简单可靠,适用于你的数据结构 Object.assign(localCapitalInfo, JSON.parse(JSON.stringify(sourceData))); baseFileSelectorRef.value?.cleanupResources(); } });

推荐方案3:使用 lodash(最专业)

typescript

import { cloneDeep } from 'lodash-es'; watch(infoDialogVisible, (newValue) => { if (newValue) { const sourceData = capitalInfoStore.currentSelectedCapitalInfo || emptyCapitalInfo; // ✅ 专业可靠的深拷贝 Object.assign(localCapitalInfo, cloneDeep(sourceData)); baseFileSelectorRef.value?.cleanupResources(); } });

为什么structuredClone会失败?

在你的情况下,最可能的原因是:

  1. sourceData 是 Vue 的响应式代理对象

    • structuredClone无法克隆 Proxy 对象

    • 需要使用toRaw()先获取原始对象

  2. 对象中包含 Vue 的内部属性

    • __v_skip__v_isRef

    • 这些是 Vue 内部使用的,不可克隆

  3. 可能有循环引用

    • 虽然你的数据结构看起来没有,但 Vue 的响应式系统可能会创建

完整修复示例

typescript

import { watch } from 'vue'; import { cloneDeep } from 'lodash-es'; // 监视信息对话框状态,同步数据 watch(infoDialogVisible, (newValue) => { if (newValue) { // 对话框打开时,从store复制数据到本地副本 const sourceData = capitalInfoStore.currentSelectedCapitalInfo || emptyCapitalInfo; // ✅ 最佳方案:使用 lodash 的 cloneDeep Object.assign(localCapitalInfo, cloneDeep(sourceData)); // 或者使用 JSON 方法(如果确定没有函数、Date等特殊类型) // Object.assign(localCapitalInfo, JSON.parse(JSON.stringify(sourceData))); // 文件选择器实例清空资源,以便可以继续选择文件,不然会提示文件限制 baseFileSelectorRef.value?.cleanupResources(); } });

总结

方案优点缺点推荐度
JSON.parse(JSON.stringify())简单、无需依赖、处理大部分基本类型丢失函数、Date、undefined等⭐⭐⭐⭐
lodash.cloneDeep功能全面、可靠、保留更多类型需要引入第三方库⭐⭐⭐⭐⭐
toRaw + structuredClone浏览器原生、性能好需要处理Vue代理、可能仍有兼容问题⭐⭐
自定义深拷贝函数完全可控、可定制实现复杂、容易出错⭐⭐

根据你的错误信息和数据结构,推荐用lodash.cloneDeepJSON.parse(JSON.stringify())

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/12 10:42:14

轻量级CAD解决方案:免费开源工具的全方位测评

轻量级CAD解决方案&#xff1a;免费开源工具的全方位测评 【免费下载链接】LitCAD A very simple CAD developed by C#. 项目地址: https://gitcode.com/gh_mirrors/li/LitCAD 在CAD设计领域&#xff0c;专业软件往往伴随着高昂的授权费用和复杂的学习曲线&#xff0c;这…

作者头像 李华
网站建设 2026/5/14 3:22:37

探索TVBoxOSC:解锁电视盒子的复古游戏潜能

探索TVBoxOSC&#xff1a;解锁电视盒子的复古游戏潜能 【免费下载链接】TVBoxOSC TVBoxOSC - 一个基于第三方项目的代码库&#xff0c;用于电视盒子的控制和管理。 项目地址: https://gitcode.com/GitHub_Trending/tv/TVBoxOSC 在数字娱乐多元化的今天&#xff0c;如何将…

作者头像 李华
网站建设 2026/5/12 13:10:39

Qwen3-VL-4B Pro效果展示:旅游景点照→文化背景解读+游览建议

Qwen3-VL-4B Pro效果展示&#xff1a;旅游景点照→文化背景解读游览建议 1. 这不是“看图说话”&#xff0c;而是真正读懂一张旅行照片 你有没有试过拍下一座古塔、一扇雕花木门、或是一处人迹罕至的石窟&#xff0c;却对它背后的故事一无所知&#xff1f;手机相册里存着上百…

作者头像 李华
网站建设 2026/5/12 13:10:39

4个维度掌握Unity海洋渲染技术:Ceto进阶实战指南

4个维度掌握Unity海洋渲染技术&#xff1a;Ceto进阶实战指南 【免费下载链接】Ceto Ceto: Ocean system for Unity 项目地址: https://gitcode.com/gh_mirrors/ce/Ceto Unity海洋渲染技术是现代游戏开发中打造沉浸式水环境的核心环节。Ceto作为专为Unity设计的开源海洋系…

作者头像 李华
网站建设 2026/5/12 13:11:18

从零到一:Vivado与Vitis协同开发的五大实战技巧

从零到一&#xff1a;Vivado与Vitis协同开发的五大实战技巧 在FPGA和嵌入式系统开发领域&#xff0c;Xilinx的Vivado和Vitis工具链已经成为行业标准。但对于初学者而言&#xff0c;这两个工具的协同工作流程常常令人望而生畏。本文将分享五个关键实战技巧&#xff0c;帮助开发者…

作者头像 李华