主题
useRefHistory
跟踪引用的更改历史记录,还提供撤消和重做功能
¥Track the change history of a ref, also provides undo and redo functionality
通过 Vue School 的免费视频课程学习 useRefHistory!示例
用法
¥Usage
ts
import { useRefHistory } from '@vueuse/core'
import { ref } from 'vue'
const counter = ref(0)
const { history, undo, redo } = useRefHistory(counter)
在内部,watch
用于在参考值修改时触发历史点。这意味着历史点会在同一 "tick" 中异步触发批量修改。
¥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
将参考值重置为最后一个历史点。
¥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
选项进行设置。
¥useRefHistory
only embeds the minimal clone function x => JSON.parse(JSON.stringify(x))
. To use a full featured or custom clone function, you can set up via the clone
options.
例如,使用 structuredClone:
¥For example, using structuredClone:
ts
import { useRefHistory } from '@vueuse/core'
const refHistory = useRefHistory(target, { clone: structuredClone })
或者使用 洛达什的 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
历史刷新时间
¥History Flush Timing
来自 Vue 的文档:Vue 的反应式性系统会缓冲无效的效果并异步刷新它们,以避免在同一个 "tick" 中发生许多状态突变时不必要的重复调用。
¥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 观察者的默认值保持一致。这也有助于避免常见问题,例如作为对引用值的多步更新的一部分生成的多个历史记录点,可能会破坏应用状态的不变量。如果你需要在同一个 "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 = 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)
为多个同步操作生成单个历史点
¥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)
改变原始引用值。
¥Another option is to avoid mutating the original ref value using arr.value = [...arr.value].splice(1,1)
.
推荐读物
¥Recommended Readings
类型声明
显示类型声明
typescript
export interface UseRefHistoryOptions<Raw, Serialized = Raw>
extends ConfigurableEventFilter {
/**
* Watch for deep changes, default to false
*
* When set to true, it will also create clones for values store in the history
*
* @default false
*/
deep?: boolean
/**
* The flush option allows for greater control over the timing of a history point, default to 'pre'
*
* Possible values: 'pre', 'post', 'sync'
* It works in the same way as the flush option in watch and watch effect in vue reactivity
*
* @default 'pre'
*/
flush?: "pre" | "post" | "sync"
/**
* Maximum number of history to be kept. Default to unlimited.
*/
capacity?: number
/**
* Clone when taking a snapshot, shortcut for dump: JSON.parse(JSON.stringify(value)).
* Default to false
*
* @default false
*/
clone?: boolean | CloneFn<Raw>
/**
* Serialize data into the history
*/
dump?: (v: Raw) => Serialized
/**
* Deserialize data from the history
*/
parse?: (v: Serialized) => Raw
}
export interface UseRefHistoryReturn<Raw, Serialized>
extends UseManualRefHistoryReturn<Raw, Serialized> {
/**
* A ref representing if the tracking is enabled
*/
isTracking: Ref<boolean>
/**
* Pause change tracking
*/
pause: () => void
/**
* Resume change tracking
*
* @param [commit] if true, a history record will be create after resuming
*/
resume: (commit?: boolean) => void
/**
* A sugar for auto pause and auto resuming within a function scope
*
* @param fn
*/
batch: (fn: (cancel: Fn) => void) => void
/**
* Clear the data and stop the watch
*/
dispose: () => void
}
/**
* Track the change history of a ref, also provides undo and redo functionality.
*
* @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>