useRefHistory
跟踪 ref 的变化历史,还提供了撤销和重做功能。
通过 Vue School 的免费视频课程了解 useRefHistory!Demo
用法
import { useRefHistory } from '@vueuse/core'
import { ref } from 'vue'
const counter = ref(0)
const { history, undo, redo } = useRefHistory(counter)
在内部,使用 watch
来触发历史记录点,当 ref 的值被修改时。这意味着历史记录点是异步触发的,将相同的修改批处理在同一“时刻”内。
counter.value += 1
await nextTick()
console.log(history.value)
/* [
{ snapshot: 1, timestamp: 1601912898062 },
{ snapshot: 0, timestamp: 1601912898061 }
] */
您可以使用 undo
将 ref 的值重置为上一个历史记录点。
console.log(counter.value) // 1
undo()
console.log(counter.value) // 0
对象 / 数组
当使用对象或数组时,由于更改它们的属性不会更改引用,因此不会触发提交。要跟踪属性更改,您需要传递 deep: true
。它将为每个历史记录创建克隆。
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' } }
] */
自定义克隆函数
useRefHistory
仅嵌入了最小的克隆函数 x => JSON.parse(JSON.stringify(x))
。要使用全功能或自定义克隆函数,您可以通过 clone
选项进行设置。
例如,使用 structuredClone:
import { useRefHistory } from '@vueuse/core'
const refHistory = useRefHistory(target, { clone: structuredClone })
或使用 lodash 的 cloneDeep
:
import { useRefHistory } from '@vueuse/core'
import { cloneDeep } from 'lodash-es'
const refHistory = useRefHistory(target, { clone: cloneDeep })
或更轻量级的 klona
:
import { useRefHistory } from '@vueuse/core'
import { klona } from 'klona'
const refHistory = useRefHistory(target, { clone: klona })
自定义转储和解析函数
您可以传递自定义函数来控制序列化和解析,而不是使用 clone
选项。如果您不需要历史值是对象,这可以在撤销时节省一个额外的克隆。如果您希望快照已经被字符串化以便例如保存到本地存储,这也很有用。
import { useRefHistory } from '@vueuse/core'
const refHistory = useRefHistory(target, {
dump: JSON.stringify,
parse: JSON.parse,
})
历史记录容量
默认情况下,我们会保留所有的历史记录(无限),直到您明确清除它们,您可以通过 capacity
选项设置要保留的历史记录的最大数量。
const refHistory = useRefHistory(target, {
capacity: 15, // 限制为 15 条历史记录
})
refHistory.clear() // 明确清除所有的历史记录
历史记录刷新时机
来自 Vue 文档:Vue 的响应性系统会缓冲失效的效果并异步刷新它们,以避免在同一“时刻”发生许多状态突变时不必要的重复调用。
与 watch
类似,您可以使用 flush
选项修改刷新时机。
const refHistory = useRefHistory(target, {
flush: 'sync', // 选项 'pre'(默认),'post' 和 'sync'
})
默认值是 'pre'
,以使此组合与 Vue 观察器的默认值保持一致。这也有助于避免常见问题,比如在同一“时刻”内作为 ref 值多步更新的一部分生成了几个历史记录点,这可能会破坏应用程序状态的不变性。如果需要在同一“时刻”内创建多个历史记录点,则可以使用 commit()
。
const r = ref(0)
const { history, commit } = useRefHistory(r)
r.value = 1
commit()
r.value = 2
commit()
console.log(history.value)
/* [
{ snapshot: 2 },
{ snapshot: 1 },
{ snapshot: 0 },
] */
另一方面,当使用 flush: 'sync'
时,您可以使用 batch(fn)
为多个同步操作生成单个历史记录点。
const r = ref({ names: [], version: 1 })
const { history, batch } = useRefHistory(r, { flush: 'sync' })
batch(() => {
r.value.names.push('Lena')
r.value.version++
})
console.log(history.value)
/* [
{ snapshot: { names: [ 'Lena' ], version: 2 },
{ snapshot: { names: [], version: 1 },
] */
如果使用了 { flush: 'sync', deep: true }
,batch
在对数组进行可变的 splice
时也很有用。splice
可以生成最多三个原子操作,这些操作将被推送到 ref 历史记录中。
const arr = ref([1, 2, 3])
const { history, batch } = useRefHistory(arr, { deep: true, flush: 'sync' })
batch(() => {
arr.value.splice(1, 1) // batch 确保只生成一个历史记录点
})
另一个选项是避免直接改变原始的 ref 值,而是使用 arr.value = [...arr.value].splice(1,1)
。
推荐阅读
类型声明
显示类型声明
export interface UseRefHistoryOptions<Raw, Serialized = Raw>
extends ConfigurableEventFilter {
/**
* 监听深层变化,默认为 false
*
* 当设置为 true 时,它还将为存储在历史记录中的值创建克隆
*
* @default false
*/
deep?: boolean
/**
* flush 选项允许更大的控制历史点的时间,默认为 'pre'
*
* 可能的值:'pre', 'post', 'sync'
* 它的工作方式与 vue 响应性中 watch 和 watch effect 中的 flush 选项相同
*
* @default 'pre'
*/
flush?: "pre" | "post" | "sync"
/**
* 要保留的历史记录的最大数量。默认为无限。
*/
capacity?: number
/**
* 在获取快照时进行克隆,快捷方式为 dump: JSON.parse(JSON.stringify(value))。
* 默认为 false
*
* @default false
*/
clone?: boolean | CloneFn<Raw>
/**
* 将数据序列化到历史记录中
*/
dump?: (v: Raw) => Serialized
/**
* 从历史记录中反序列化数据
*/
parse?: (v: Serialized) => Raw
}
export interface UseRefHistoryReturn<Raw, Serialized>
extends UseManualRefHistoryReturn<Raw, Serialized> {
/**
* 表示是否启用跟踪的 ref
*/
isTracking: Ref<boolean>
/**
* 暂停更改跟踪
*/
pause: () => void
/**
* 恢复更改跟踪
*
* @param [commit] 如果为 true,在恢复后将创建一个历史记录
*/
resume: (commit?: boolean) => void
/**
* 在函数范围内提供自动暂停和自动恢复的语法糖
*
* @param fn
*/
batch: (fn: (cancel: Fn) => void) => void
/**
* 清除数据并停止观察
*/
dispose: () => void
}
/**
* 跟踪 ref 的变更历史记录,并提供撤销和重做功能。
*
* @see https://vueuse.org/useRefHistory
* @param source
* @param options
*/
export declare function useRefHistory<Raw, Serialized = Raw>(
source: Ref<Raw>,
options?: UseRefHistoryOptions<Raw, Serialized>,
): UseRefHistoryReturn<Raw, Serialized>