我是这样重置数据的
数据重置,你肯定不陌生,这有啥可讲的,不就是 复制一份 再重新更新上去,哎哎~ 今天讲的可不是让你手动复制一份手动更新
前言
相信大家都有遇到这样的一个需求,例如 form 表单,点击编辑只要没有保存,取消时 数据应该还原为之前的数据
在此之前你可能会先 clone一个数据副本,取消再重新赋值上去,嗯~ 没问题 确实可以
但是即便是你把它封装成一个方法,到最后不还是得 import 来 import 过去,还要再 调用。好像有点麻烦的样子👀
本文适合结合上一篇文章 我是这样写TS的 来阅读
传统
import { deepClone } from "lodash"
// 假设 数据是从 后端获取
// useUser.ts
export function useUser() {
  return {
    id: 1,
    name: "至尊宝",
    age: 27,
    lover: "紫霞仙子"
  }
}
// user.vue
const user = ref({
  id: 0,
  name: "",
  age: 0,
  lover: ""
})
// 获取用户
const data = useUser()
// copy
const originData = deepClone(data);
user.value = data
// 重置
function reset() {
  user.value = originData
}
确实是没问题,继续看不一样的
不一样的
抽象类 AbstractEntity
在抽象类上添加 reset 方法
// abstractEntity.ts
const valueWeakMap = new WeakMap<AbstractEntity, EntityJSON<AbstractEntity>>()
export abstract class AbstractEntity {
  constructor() {
    // 注意这里
		const self = this
    window.setTimeout(function () {
      valueWeakMap.set(self, self.toJSON())
    })
  }
  public reset() {
    // 注意这里
    const context = toRaw(this)
    const data = valueWeakMap.get(context)!
    for (const [key, value] of Object.entries(data)) {
      ;(this as Record<string, unknown>)[key] = value
    }
  }
}
这里可以思考一下 为什么 要 setTimeout 延时 和 reset 方法内 为何要 toRaw
实体
// user.entity.ts
export class UserEntity extends AbstractEntity {
  id: number
  name: string
  age: number
  lover: string
}
视图
// hooks.ts
import { plainToInstance } from 'class-transformer'
// 假设已获取到后端数据
export function useUser() {
  const data = new UserEntity()
  Promise.resolve({name: "至尊宝", age: 27, lover: "紫霞仙子"}).then(function (response) {
  	data.value = ref(plainToInstance(UserEntity, response))
  })
  return data
}
<template>
	<form>
    <div>
    	<label>用户名</label>
    	<input v-model:value="user.name" />
  	</div>
    <div>
    	<label>年龄</label>
    	<input v-model:value="user.age" />
  	</div>
    <div>
    	<label>喜欢</label>
    	<input v-model:value="user.lover" />
  	</div>
    <div>
    	<button @click="user.reset()">重置</button>
  	</div>
  </form>
</template>
<script lang="ts" setup>
import { useUser } from './hooks'
const user = useUser()
</script>
怎么样看起来不错吧,很简洁 清晰
问题
WeakMap 是什么?
WeakMap这个大家应该很少见,确实 我也少见这个,ES6看了不少,但是这个确实没怎么研究过
先看看MDN怎么解释
WeakMap是一种键值对的集合,其中的键必须是对象或非全局注册的符号,且值可以是任意的 JavaScript 类型,并且不会创建对它的键的强引用。换句话说,一个对象作为
WeakMap的键存在,不会阻止该对象被垃圾回收。一旦一个对象作为键被回收,那么在WeakMap中相应的值便成为了进行垃圾回收的候选对象,只要它们没有其他的引用存在。唯一可以作为WeakMap的键的原始类型是非全局注册的符号,因为非全局注册的符号是保证唯一的,并且不能被重新创建。
WeakMap允许将数据与对象相关联,而不阻止键对象被垃圾回收,即使值引用了键。然而,WeakMap并不允许观察其键的生命周期,这就是为什么它不允许枚举;如果WeakMap提供了任何获得其键的列表的方法,那么这些列表将会依赖于垃圾回收的状态,这引入了不确定性。如果你想要可以获取键的列表,你应该使用Map而不是WeakMap。
说白了WeakMap 的 key 如果是对象,且这个对象被回收后 那么 所对应的 value 也就被 回收了,那么我们就可以利用这个特性去做数据缓存的操作 嘿嘿嘿🤓
我们都知道 ref 的变量 在 vue 组件销毁时 会自动的也会去销毁变量那么
当 获取后端数据 初始化 class 时 则可以 把原始数据缓存起来,重置的时候 拿出来更新掉,ref 销毁时,则 自动解除了引用 WeakMap 则自动回收 value 值
为什么在 constructor 里要 setTimeout?
其实这是跟 微任务/宏任务 有关系的
new 的时候只是会初始化一个普通类而已。如果是初始化数据则(注意此时数据并没有被填充),此时 this 是一个 未被填充数据的空对象 所以需要 setTimeout 或者 Promise.then 做异步操作,否则self.toJSON()返回的是空对象
 为什么在 reset 方法内 需要 toRaw(this)?
因为此时 this 是一个被 vue 代理的 对象,所以 原来的 this 内存地址 跟 当前 this 内存地址不一致 所以 valueWeakMap.get(this)获取到的 是 undefined
在 constructor 里 WeakMap 存的 this 是 未被代理的原生对象,因为那个时候 plainToInstance 还在初始化 class
总结

Class 优雅永不过时