跳到主要內容

useRefHistory

類別
匯出大小
1.09 kB
上次變更
4 個月前
相關

追蹤 ref 的變更歷史記錄,並提供復原和重做功能

透過 Vue School 的免費影片課程學習 useRefHistory!

示範

計數: 0
/

歷史記錄 (示範中限制為 10 筆記錄)
2025-03-10 03:47:03{ value: 0 }

用法

ts
import { useRefHistory } from '@vueuse/core'
import { shallowRef } from 'vue'

const counter = shallowRef(0)
const { history, undo, redo } = useRefHistory(counter)

在內部,watch 用於在修改 ref 值時觸發歷史記錄點。這表示歷史記錄點會非同步觸發,並批次處理同一個「tick」中的修改。

ts
counter.value += 1

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

您可以使用 undo 將 ref 值重設為最後一個歷史記錄點。

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

物件 / 陣列

當處理物件或陣列時,由於變更其屬性不會變更參考,因此不會觸發提交。若要追蹤屬性變更,您需要傳遞 deep: true。它會為每個歷史記錄建立副本。

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

自訂複製函式

useRefHistory 僅嵌入最小的複製函式 x => JSON.parse(JSON.stringify(x))。若要使用完整功能或自訂複製函式,您可以透過 clone 選項進行設定。

例如,使用 structuredClone

ts
import { useRefHistory } from '@vueuse/core'

const refHistory = useRefHistory(target, { clone: structuredClone })

或使用 lodash 的 cloneDeep

ts
import { useRefHistory } from '@vueuse/core'
import { cloneDeep } from 'lodash-es'

const refHistory = useRefHistory(target, { clone: cloneDeep })

或更輕量的 klona

ts
import { useRefHistory } from '@vueuse/core'
import { klona } from 'klona'

const refHistory = useRefHistory(target, { clone: klona })

自訂 Dump 和 Parse 函式

除了使用 clone 選項之外,您還可以傳遞自訂函式來控制序列化和解析。如果您不需要歷史記錄值為物件,這可以節省在復原時額外的複製。如果您想要讓快照已經字串化以儲存到本機儲存空間,這也很有用。

ts
import { useRefHistory } from '@vueuse/core'

const refHistory = useRefHistory(target, {
  dump: JSON.stringify,
  parse: JSON.parse,
})

歷史記錄容量

預設情況下,我們將保留所有歷史記錄 (無限制),直到您明確清除它們,您可以設定要保留的最大歷史記錄量,透過 capacity 選項。

ts
const refHistory = useRefHistory(target, {
  capacity: 15, // limit to 15 history records
})

refHistory.clear() // explicitly clear all the history

歷史記錄 Flush Timing

Vue 的文件: Vue 的響應系統會緩衝失效的效果,並非同步刷新它們,以避免在同一個「tick」中發生許多狀態變更時不必要的重複調用。

watch 相同,您可以使用 flush 選項修改刷新時序。

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

預設值為 'pre',使此 composable 與 Vue 監看器的預設值對齊。這也有助於避免常見問題,例如,作為 ref 值多步驟更新的一部分產生多個歷史記錄點,這可能會破壞應用程式狀態的不變性。如果您需要在同一個「tick」中建立多個歷史記錄點,您可以使用 commit()

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) 為多個同步操作產生單個歷史記錄點

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 最多可以產生三個原子操作,這些操作將被推送到 ref 歷史記錄中。

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
})

另一個選項是避免變更原始 ref 值,使用 arr.value = [...arr.value].splice(1,1)

類型宣告

顯示類型宣告
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.dev.org.tw/useRefHistory
 * @param source
 * @param options
 */
export declare function useRefHistory<Raw, Serialized = Raw>(
  source: Ref<Raw>,
  options?: UseRefHistoryOptions<Raw, Serialized>,
): UseRefHistoryReturn<Raw, Serialized>

原始碼

原始碼示範文件

貢獻者

Anthony Fu
Matias Capeletto
Anthony Fu
IlyaL
Lov`u`e
Kyrie890514
sun0day
Eduardo Wesley
vaakian X
Hollis Wu
Bruno Perel
wheat
webfansplz
Roman Harmyder
Alex Kozack
Antério Vieira

變更日誌

v12.0.0-beta.1 於 2024/11/21
0a9ed - feat!: 移除 Vue 2 支援、最佳化 bundle 並清理 (#4349)
v10.8.0 於 2024/2/20
a086e - fix: 更嚴格的類型

根據 MIT 許可證發布。