useRefHistory 是 Vue 3 中的一个自定义 hook,它允许你为一个值创建可撤销和重做的历史记录。当我们想要跟踪一个值的变化历史,并能够在需要时撤销和重做这些变化时,这非常有用。
使用 useRefHistory,我们可以获取一个 ref,它包装了初始值,以及可用于撤销和重做的方法。我们可以在任何时候修改这个 ref,并在需要时撤销或重做这些修改。这个 hook 在处理表单和其他需要跟踪历史记录的场景中非常有用。
下面是 useRefHistory 的一个简单示例:
import { useRefHistory } from '@vueuse/core'
export default {
setup() {
const value = ref(0)
const { undo, redo, canUndo, canRedo } = useRefHistory(value)
const increment = () => value.value++
const decrement = () => value.value--
return {
value,
increment,
decrement,
undo,
redo,
canUndo,
canRedo,
}
}
}
在这个示例中,我们使用 useRefHistory 包装了一个 ref,并将其用于跟踪变量 value 的历史记录。我们还使用 undo 和 redo 方法来撤销和重做修改,并使用 canUndo 和 canRedo 方法来确定是否可以进行这些操作。
在 useRefHistory 内部使用了 Vue 3 的 watch 函数来监听目标 ref 的变化,从而触发记录 history point。在这个过程中,多个 ref 变化可能会被批量处理,因为 watch 函数会将它们放入异步队列中,等到下一个事件循环才会依次执行。这种批量处理可以提高性能,避免不必要的计算和渲染,同时也可以避免不必要的 history 记录。
Objects / arrays
在处理对象或数组时,由于改变它们的属性不会改变引用,因此不会触发历史记录的创建。如果要跟踪属性的更改,需要将 deep 参数设置为 true。这将为每个历史记录创建克隆。简单来说,如果你要追踪对象或数组的属性的更改历史,需要使用 deep 选项,这会将每个历史记录与之前的克隆进行比较,并在属性更改时触发历史记录的创建。
const state = ref({
foo: 1,
bar: 'bar',
})
const { history, undo, redo } = useRefHistory(state, {
deep: true,
})
state.value.foo = 2
await nextTick()
console.log(history.value)
/* [
{ snapshot: { foo: 2, bar: 'bar' } },
{ snapshot: { foo: 1, bar: 'bar' } }
] */
自定义克隆方法
在 JavaScript 中,对象和数组是引用类型,它们的值存储在堆内存中,并且在使用时通过引用来访问它们。当对象或数组被传递给一个函数或另一个变量时,实际上是传递了一个指向它们在内存中的地址。因此,如果我们修改了对象或数组的属性,那么实际上是修改了内存中的同一个对象或数组,而不是创建了一个新的对象或数组。
在useRefHistory 中,如果需要追踪对象或数组属性的变化,需要对每个历史记录进行克隆。默认情况下,会使用浅克隆,即只会克隆对象或数组本身,而不会克隆它们的属性。为了解决这个问题,useRefHistory 提供了一个选项 customCloneFn,允许你传入一个自定义的克隆函数来实现更深层次的克隆。自定义克隆函数将在每个历史记录点上调用,并且接受要克隆的值作为参数,返回克隆后的值。通过使用自定义克隆函数,你可以实现完全自定义的克隆逻辑。
例如,使用 lodash 的 cloneDeep 方法:
import { cloneDeep } from 'lodash-es'
import { useRefHistory } from '@vueuse/core'
const refHistory = useRefHistory(target, { dump: cloneDeep })
自定义 dump 和 parse
在序列化和反序列化的上下文中,dump 和 parse 函数是指将数据从一种格式转换为另一种格式的过程。
dump 函数是将数据从内部格式转换为序列化格式的函数,该格式可以在程序之外传输或存储。例如,可以使用 JSON.stringify() 函数将 JavaScript 对象“转储”为 JSON 字符串。
parse 函数的作用与 dump 相反:它将序列化后的数据解析成内部格式,以便程序可以使用。例如,使用 JSON.parse() 函数将 JSON 字符串解析为 JavaScript 对象。
自定义 dump 和 parse 指的是定义自己的序列化和反序列化函数,以便以特定于程序需求的方式处理数据。例如,可能会定义一个自定义dump函数,将复杂的数据结构转换为更简单的格式,以便更轻松地通过网络进行传输。同样,可能会定义一个自定义parse函数,将序列化的数据转换回原始的复杂数据结构。
除了使用 clone 参数外,useRefHistory 还可以通过传递自定义 dump 和 parse 函数来控制序列化和解析。如果不需要历史记录值是对象,则可以避免在撤消时进行额外的克隆。这在想要将快照字符串化以保存到本地存储等情况下也很有用。
例如:
import { useRefHistory } from '@vueuse/core'
const refHistory = useRefHistory(target, {
dump: JSON.stringify,
parse: JSON.parse,
})
配置选项
useRefHistory 函数的选项对象接口定义,可以配置选项,包括:
- deep: 默认情况下,
deep参数的值是false,意味着在监视(watch)一个 ref 的变化时只会监视其一级属性的变化。但是如果你将deep参数的值设为true,那么在监视 ref 的变化时就会递归地监视其所有子属性的变化。当值发生变化时,不仅仅是当前的值被保存在历史记录中,它所有子属性的值也会被保存。由于这会造成历史记录的变大,导致性能下降,因此只有在必要的时候才应该将deep参数设置为true。另外,当deep参数为true时,会为所有被保存在历史记录中的值创建一个副本,以保证历史记录中的值不受外部修改的影响。 - flush:
flush选项的工作方式与 Vue 的响应式系统中的watch和watchEffect函数的flush选项相同。在 Vue 中,flush 选项用于控制侦听器或副作用函数的更新时间。三个可能的选项是 "pre"、"post" 和 "sync",分别表示在异步更新之前、之后或同步更新。在useRefHistory中,flush 选项也用于控制历史记录点的时间。 - capacity: 设置最大的历史记录数量。
- clone: 见上文
- dump: 见上文
- parse: 见上文