Skip to content

useRefHistory

类别
导出大小
1.18 kB
最近修改
2 days ago
相关

跟踪引用的更改历史记录,还提供撤消和重做功能

🌐 Track the change history of a ref, also provides undo and redo functionality

通过 Vue School 的免费视频课程学习 useRefHistory!

示例

Count: 0
/

History (limited to 10 records for demo)
2026-02-08 18:48:04{ value: 0 }

用法

🌐 Usage

ts
import { 
useRefHistory
} from '@vueuse/core'
import {
shallowRef
} from 'vue'
const
counter
=
shallowRef
(0)
const {
history
,
undo
,
redo
} =
useRefHistory
(
counter
)

在内部,当 ref 值被修改时,watch 用于触发一个历史记录点。这意味着历史记录点是异步触发的,并将同一“时刻”的修改进行批处理。

🌐 Internally, watch is used to trigger a history point when the ref value is modified. This means that history points are triggered asynchronously batching modifications in the same "tick".

ts
counter
.
value
+= 1
await
nextTick
()
console
.
log
(
history
.
value
)
/* [ { snapshot: 1, timestamp: 1601912898062 }, { snapshot: 0, timestamp: 1601912898061 } ] */

你可以使用 undo 将 ref 值重置到上一次的历史点。

🌐 You can use undo to reset the ref value to the last history point.

ts
console
.
log
(
counter
.
value
) // 1
undo
()
console
.
log
(
counter
.
value
) // 0

对象/数组

🌐 Objects / arrays

在处理对象或数组时,由于修改它们的属性不会改变引用,所以不会触发提交。要跟踪属性的变化,你需要传入 deep: true。它会为每个历史记录创建克隆。

🌐 When working with objects or arrays, since changing their attributes does not change the reference, it will not trigger the committing. To track attribute changes, you would need to pass deep: true. It will create clones for each history record.

ts
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' } } ] */

自定义克隆函数

🌐 Custom Clone Function

useRefHistory 仅嵌入了最小的克隆函数 x => JSON.parse(JSON.stringify(x))。要使用完整功能或自定义的克隆函数,你可以通过 clone 选项进行设置。

例如,使用 structuredClone

🌐 For example, using structuredClone:

ts
import { 
useRefHistory
} from '@vueuse/core'
const
refHistory
=
useRefHistory
(target, {
clone
:
structuredClone
})

或者使用 lodash 的 cloneDeep:

🌐 Or by using lodash's cloneDeep:

ts
import { 
useRefHistory
} from '@vueuse/core'
import {
cloneDeep
} from 'lodash-es'
const
refHistory
=
useRefHistory
(target, {
clone
:
cloneDeep
})

或者一个更轻量的 klona

🌐 Or a more lightweight klona:

ts
import { 
useRefHistory
} from '@vueuse/core'
import {
klona
} from 'klona'
const
refHistory
=
useRefHistory
(target, {
clone
:
klona
})

自定义转储和解析函数

🌐 Custom Dump and Parse Function

你可以不用 clone 选项,而是传入自定义函数来控制序列化和解析。如果你不需要将历史值作为对象,这样在撤销时可以节省一次额外的克隆操作。如果你希望快照已经是字符串格式以便例如保存到本地存储,这也非常有用。

🌐 Instead of using the clone options, you can pass custom functions to control the serialization and parsing. In case you do not need history values to be objects, this can save an extra clone when undoing. It is also useful in case you want to have the snapshots already stringified to be saved to local storage for example.

ts
import { 
useRefHistory
} from '@vueuse/core'
const
refHistory
=
useRefHistory
(target, {
dump
:
JSON
.
stringify
,
parse
:
JSON
.
parse
,
})

历史容量

🌐 History Capacity

我们默认会保留所有历史记录(无限制),直到你明确清除为止,你可以通过 capacity 选项设置要保留的历史记录的最大数量。

🌐 We will keep all the history by default (unlimited) until you explicitly clear them up, you can set the maximal amount of history to be kept by capacity options.

ts
const 
refHistory
=
useRefHistory
(target, {
capacity
: 15, // limit to 15 history records
})
refHistory
.
clear
() // explicitly clear all the history

历史 WatchOptionFlush 时间

🌐 History WatchOptionFlush Timing

来自 Vue 的文档:Vue 的响应式系统会缓冲失效的副作用,并异步刷新它们,以避免在同一个“时钟周期”内发生大量状态变更时不必要的重复调用。

🌐 From Vue's documentation: Vue's reactivity system buffers invalidated effects and flush them asynchronously to avoid unnecessary duplicate invocation when there are many state mutations happening in the same "tick".

watch 相同,你可以使用 flush 选项来修改刷新时间。

🌐 In the same way as watch, you can modify the flush timing using the flush option.

ts
const 
refHistory
=
useRefHistory
(target, {
flush
: 'sync', // options 'pre' (default), 'post' and 'sync'
})

默认值是 'pre',以使这个可组合组件与 Vue 的观察者默认行为保持一致。这也有助于避免常见问题,例如在对 ref 值进行多步更新时生成多个历史记录点,可能会破坏应用状态的不可变性。如果需要在同一个“tick”中创建多个历史记录点,可以使用 commit()

🌐 The default is 'pre', to align this composable with the default for Vue's watchers. This also helps to avoid common issues, like several history points generated as part of a multi-step update to a ref value that can break invariants of the app state. You can use commit() in case you need to create multiple history points in the same "tick"

ts
const 
r
=
shallowRef
(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) 为多个同步操作生成一个历史记录点

🌐 On the other hand, when using flush 'sync', you can use batch(fn) to generate a single history point for several sync operations

ts
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 },在对数组进行可变的 splice 时,batch 也很有用。splice 可以生成最多三个原子操作,这些操作将被推送到引用历史中。

🌐 If { flush: 'sync', deep: true } is used, batch is also useful when doing a mutable splice in an array. splice can generate up to three atomic operations that will be pushed to the ref history.

ts
const 
arr
=
ref
([1, 2, 3])
const {
history
,
batch
} =
useRefHistory
(
arr
, {
deep
: true,
flush
: 'sync' })
batch
(() => {
arr
.
value
.
splice
(1, 1) // batch ensures only one history point is generated
})

另一种选择是使用 arr.value = [...arr.value].splice(1,1) 来避免修改原始的 ref 值。

🌐 Another option is to avoid mutating the original ref value using arr.value = [...arr.value].splice(1,1).

🌐 Recommended Readings