useRefHistory

useRefHistory

refの変更履歴を追跡し、元に戻す(undo)およびやり直す(redo)機能を提供します

Vue Schoolのこの無料ビデオレッスンでuseRefHistoryを学びましょう!

使用法

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

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

内部的には、watchが使用され、refの値が変更されたときに履歴ポイントをトリガーします。これは、同じ「tick」での変更を非同期にバッチ処理することを意味します。

// @include: usage
// ---cut---
counter.value += 1

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

undoを使用して、refの値を最後の履歴ポイントにリセットできます。

// @include: usage
// ---cut---
console.log(counter.value) // 1
undo()
console.log(counter.value) // 0

オブジェクト / 配列

オブジェクトや配列を扱う場合、その属性を変更しても参照が変わらないため、コミットをトリガーしません。属性の変更を追跡するには、deep: trueを渡す必要があります。これにより、各履歴レコードのクローンが作成されます。

import { useRefHistory } from '@vueuse/core'
// ---cut---
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オプションで設定できます。

import { useRefHistory } from '@vueuse/core'
// ---cut---
const refHistory = useRefHistory(target, {
  capacity: 15, // 履歴レコードを15に制限
})

refHistory.clear() // 明示的にすべての履歴をクリア

履歴のWatchOptionFlushタイミング

Vueのドキュメントによると、Vueのリアクティブシステムは無効化されたエフェクトをバッファし、同じ「tick」で多くの状態変化が発生したときに不要な重複呼び出しを避けるために非同期でフラッシュします。

watchと同様に、flushオプションを使用してフラッシュタイミングを変更できます。

import { useRefHistory } from '@vueuse/core'
// ---cut---
const refHistory = useRefHistory(target, {
  flush: 'sync', // オプション 'pre' (デフォルト), 'post' および 'sync'
})

デフォルトは'pre'で、これはVueのウォッチャーのデフォルトに合わせています。これにより、アプリの状態の不変条件を破る可能性のある複数ステップの更新の一部として生成される複数の履歴ポイントの問題を回避するのに役立ちます。同じ「tick」で複数の履歴ポイントを作成する必要がある場合は、commit()を使用できます。

import { useRefHistory } from '@vueuse/core'
// ---cut---
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 },
] */

一方、フラッシュ'sync'を使用する場合、batch(fn)を使用して複数の同期操作に対して単一の履歴ポイントを生成できます。

import { useRefHistory } from '@vueuse/core'
// ---cut---
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履歴に最大3つの原子的な操作をプッシュすることができます。

import { useRefHistory } from '@vueuse/core'
// ---cut---
const arr = ref([1, 2, 3])
const { history, batch } = useRefHistory(arr, { deep: true, flush: 'sync' })

batch(() => {
  arr.value.splice(1, 1) // batchは単一の履歴ポイントのみが生成されることを保証
})

もう一つのオプションは、arr.value = [...arr.value].splice(1,1)を使用して元のref値を変更しないことです。

推奨読書