有一个经常遇到的场景就是,一个表单最后一列有个编辑按钮,点击编辑按钮之后打开表单弹窗,修改其中的数据,但是如果此弹窗再作为新增弹窗打开的时候,弹窗数据会缓存上次编辑的数据。
在 Vue 3 中,由于引入了新的响应式系统,重置数据到初始值的方法与 Vue 2 不同。当你使用 reactive 创建响应式对象时,一个常见做法是保留初始数据的引用,以便在需要时恢复数据。对于使用 reactive 或 ref 创建的响应式数据,可以采取以下方法来恢复到初始值:
方法一:直接赋值
在每次关闭弹窗的时候,手动给reactive的所有子项赋初始值。
缺点是对于复杂数据手动赋值很麻烦
const state = reactive({
name:'',
age: ''
});
// 关闭弹窗
const closeDialog = () => {
state.dialog.isShowDialog = false;
state.name = '';
state.age = '';
};
方法二:使用类
网友提供的方法,感觉比较优雅
import { ref, reactive } from 'vue';
class State{
username = '',
password = ''
}
// new state()是一个新实例,除此合并之外无其它引用,无需担心浅复制引用问题
const state = reactive(new State());
Object.assign(state, new State());
// Prefect
const stateRef = ref(new state());
stateRef.value = new state();
方法三:使用函数(推荐)
直接使用函数实现,每次重置的时候调用函数即可
const initData = () => ({
name:'',
age: ''
});
const state: any = reactive(initData());
// 重置弹窗
const resetData = () => {
Object.assign(state, initData());
};
方法四:浅拷贝与深拷贝
Object.assign
方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它是一个浅拷贝,意味着它只会拷贝源对象的属性值。
如果源对象的属性值是对象或其他引用类型的值,则目标对象会保存这些值的引用。
浅拷贝示例:
let obj1 = {
a: 1,
b: { c: 2 }
};
let obj2 = Object.assign({}, obj1);
console.log(obj2); // { a: 1, b: { c: 2 } }
obj1.a = 3;
obj1.b.c = 4;
console.log(obj2); // { a: 1, b: { c: 4 } }, 因为b是引用类型,所以obj2.b也被改变了
使用浅拷贝重置数据只适用于一层的简单数据,对于引用类型的数据是无效的
如果需要进行深拷贝,即复制对象内部的所有嵌套对象,可以使用其他方法,如使用JSON.parse(JSON.stringify())
、手写递归函数或使用第三方库(如 lodash 的_.cloneDeep)。
深拷贝示例:
let obj1 = { a: 1, b: { c: 2 } };
// 使用JSON.parse(JSON.stringify())进行深拷贝
let obj2 = JSON.parse(JSON.stringify(obj1));
obj1.a = 3;
obj1.b.c = 4;
console.log(obj2); // { a: 1, b: { c: 2 } }, obj2未受obj1的修改影响
注意:JSON.parse(JSON.stringify()) 在处理包含循环引用或不可序列化属性的对象时可能会出错。因此,它不是进行深拷贝的最佳选择。使用深拷贝可以解决响应式数据恢复初始值的问题,但是要注意深拷贝的具体实现。
最佳实践:structuredClone
在现代浏览器中,可以使用 structuredClone
方法来实现深拷贝,它是一种更高效、更安全的深拷贝方式。
以下是一个示例代码,演示如何使用 structuredClone
进行深拷贝:
const kitchenSink = {
set: new Set([1, 3, 3]),
map: new Map([[1, 2]]),
regex: /foo/,
deep: { array: [ new File(someBlobData, 'file.txt') ] },
error: new Error('Hello!')
}
kitchenSink.circular = kitchenSink
const clonedSink = structuredClone(kitchenSink)
structuredClone
可以做到:
- 拷贝无限嵌套的对象和数组
- 拷贝循环引用
- 拷贝各种各样的JavaScript类型,如Date、Set、Map、Error、RegExp、ArrayBuffer、Blob、File、ImageData等
哪些不能拷贝:
- 函数
- DOM节点
- 属性描述、setter和getter
- 对象原型链
实践:
const initData = {
name:'',
age: '',
house: {
size: '',
height: ''
}
};
const state: any = reactive(initData);
// 直接保存一份原始数据
// const sourceData = JSON.parse(JSON.stringify(state));
// 本文写到这里的时候,我才发现这个函数对于这篇文章的需求没啥用,proxy代理了,structuredClone不支持函数克隆,为啥我不一开始直接搞个变量存初始化数据呢?哈哈哈哈
const sourceData = structuredClone(state); // 会报错其实
// 重置弹窗
const resetData = () => {
state = sourceData;
};
对于赋值失败的情况
如果遇到重置数据后仍然保持了输入的数据,可能的原因有几种,这里提供一些排查和解决的建议:
确保没有引用问题:如果你在重置时直接修改了引用类型的字段(如数组或对象内部的属性),而这些字段在其他地方也被引用,那么更改可能不会如预期反映出来。确保你是正确地替换整个引用,或者深拷贝后再赋值。
异步更新问题:Vue 3 中,状态改变后,DOM 的更新可能是异步的。如果你在重置状态后立即检查 DOM 或状态,可能会看到旧的值。可以使用 nextTick 确保 DOM 已更新。