本文へジャンプ

リアクティビティー API: 上級編

shallowRef()

ref() の shallow(浅い)バージョン。

triggerRef()

浅い ref に依存するエフェクトを強制的にトリガーします。これは通常、浅い ref の内部値に対して深い変更を加えた後に使用されます。

  • ts
    function triggerRef(ref: ShallowRef): void
  • js
    const shallow = shallowRef({
      greet: 'Hello, world'
    })
    
    // 初回実行時に一度だけ "Hello, world" をログ出力する
    watchEffect(() => {
      console.log(shallow.value.greet)
    })
    
    // 浅い ref なので、これはエフェクトが発動しない
    shallow.value.greet = 'Hello, universe'
    
    // "Hello, universe" がログ出力される
    triggerRef(shallow)

customRef()

依存関係の追跡と更新のトリガーを明示的に制御して、カスタマイズされた ref を作成します。

  • ts
    function customRef<T>(factory: CustomRefFactory<T>): Ref<T>
    
    type CustomRefFactory<T> = (
      track: () => void,
      trigger: () => void
    ) => {
      get: () => T
      set: (value: T) => void
    }
  • 詳細

    customRef() はファクトリー関数を想定しており、引数として tracktrigger 関数を受け取り、getset メソッドを持つオブジェクトを返す必要があります。

    一般的に、track()get() の内部で、trigger()set() の内部で呼び出されるべきものです。しかし、これらをいつ呼び出すか、あるいは全く呼び出さないかについては、あなたが完全にコントロールできます。

  • 最新の set 呼び出しから一定のタイムアウト後にのみ値を更新する debounced ref を作成する:

    js
    import { customRef } from 'vue'
    
    export function useDebouncedRef(value, delay = 200) {
      let timeout
      return customRef((track, trigger) => {
        return {
          get() {
            track()
            return value
          },
          set(newValue) {
            clearTimeout(timeout)
            timeout = setTimeout(() => {
              value = newValue
              trigger()
            }, delay)
          }
        }
      })
    }

    コンポーネントでの使用:

    vue
    <script setup>
    import { useDebouncedRef } from './debouncedRef'
    const text = useDebouncedRef('hello')
    </script>
    
    <template>
      <input v-model="text" />
    </template>

    Playground で試す

    注意して使用してください

    customRef を使用する場合、特にゲッターを実行するたびに新しいオブジェクトのデータ型を生成する場合にはそのゲッターの戻り値に注意が必要です。これは、このような customRef が props として渡されている親コンポーネントと子コンポーネントの関係に影響します。

    親コンポーネントのレンダー関数は、別のリアクティブな状態の変化によってトリガーされる可能性があります。再レンダリング中に customRef の値は再評価され、子コンポーネントへの props として新しいオブジェクトデータ型を返します。この props は子コンポーネントで前回の値と比較され、異なるので、子コンポーネント内で customRef のリアクティブな依存関係がトリガーされます。一方で customRef のセッターが呼び出されず、その結果としてその依存関係がトリガーされなかったため、親コンポーネントのリアクティブな依存関係は実行されません。

    Playground で見る

shallowReactive()

reactive() の shallow(浅い)バージョン。

  • ts
    function shallowReactive<T extends object>(target: T): T
  • 詳細

    reactive() とは異なり、深い変換は行われません。浅いリアクティブオブジェクトでは、ルートレベルのプロパティのみがリアクティブになります。プロパティ値はそのまま保存され、公開されます。これは、ref 値を持つプロパティが自動的にアンラップされないことも意味します。

    注意して使用してください

    浅いデータ構造は、コンポーネントのルートレベルの状態にのみ使用してください。深いリアクティブオブジェクトの中にネストするのは避けましょう。一貫性のないリアクティビティーの振る舞いを持ったツリーが作成され、理解やデバッグが困難になります。

  • js
    const state = shallowReactive({
      foo: 1,
      nested: {
        bar: 2
      }
    })
    
    // ステート自身のプロパティを変更するのはリアクティブ
    state.foo++
    
    // ...しかしネストされたオブジェクトは変換しない
    isReactive(state.nested) // false
    
    // リアクティブではない
    state.nested.bar++

shallowReadonly()

readonly() の shallow(浅い)バージョン。

  • ts
    function shallowReadonly<T extends object>(target: T): Readonly<T>
  • 詳細

    readonly() とは異なり、深い変換は行われません。ルートレベルのプロパティのみが読み取り専用になります。プロパティ値はそのまま保存され、公開されます。これは、ref 値を持つプロパティが自動的にアンラップされないことも意味します。

    注意して使用してください

    浅いデータ構造は、コンポーネントのルートレベルの状態にのみ使用すべきです。深いリアクティブオブジェクトの中にネストするのは避けましょう。一貫性のないリアクティブな振る舞いをするツリーが作成され、理解やデバッグが困難になります。

  • js
    const state = shallowReadonly({
      foo: 1,
      nested: {
        bar: 2
      }
    })
    
    // ステート自身のプロパティを変更すると失敗する
    state.foo++
    
    // ...しかしネストされたオブジェクトでは動作する
    isReadonly(state.nested) // false
    
    // 動作する
    state.nested.bar++

toRaw()

Vue で作成されたプロキシの、未加工の元のオブジェクトを返します。

  • ts
    function toRaw<T>(proxy: T): T
  • 詳細

    toRaw()reactive(), readonly(), shallowReactive(), shallowReadonly() で生成したプロキシから元のオブジェクトを返せるようにします。

    これは、プロキシのアクセスやトラッキングのオーバーヘッドを発生させずに一時的に読み込んだり、変更をトリガーせずに書き込んだりするために使用できる緊急避難口です。元のオブジェクトへの永続的な参照を保持することは推奨されません。注意して使用してください。

  • js
    const foo = {}
    const reactiveFoo = reactive(foo)
    
    console.log(toRaw(reactiveFoo) === foo) // true

markRaw()

プロキシに変換されないようにオブジェクトをマークします。オブジェクト自体を返します。

  • ts
    function markRaw<T extends object>(value: T): T
  • js
    const foo = markRaw({})
    console.log(isReactive(reactive(foo))) // false
    
    // 他のリアクティブオブジェクトの中にネストされても動作します
    const bar = reactive({ foo })
    console.log(isReactive(bar.foo)) // false

    注意して使用してください

    markRaw()shallowReactive() などの浅い API を使うと、デフォルトの深いリアクティブ/読み取り専用の変換を選択的にオプトアウトして、未加工の非プロキシオブジェクトを状態グラフに埋め込むことができるようになります。これらは様々な理由で利用できます:

    • 複雑なサードパーティのクラスインスタンスや Vue のコンポーネントオブジェクトのように、単純にリアクティブにすべきではない値もあります。

    • プロキシ変換をスキップすることで、イミュータブルなデータソースで大きなリストをレンダリングするときのパフォーマンスが向上する可能性があります。

    未加工のオプトアウトはルートレベルのみであるため、ネストされた、マークされていない未加工のオブジェクトをリアクティブオブジェクトにセットし、再度アクセスすると、プロキシされたバージョンが戻ってくるので、高度とみなされます。これは、アイデンティティハザード(オブジェクトの同一性に依存する操作を、同じオブジェクトの未加工バージョンとプロキシされたバージョンの両方を使用して操作すること)につながる可能性があります。

    js
    const foo = markRaw({
      nested: {}
    })
    
    const bar = reactive({
      // `foo` は未加工としてマークされるが foo.nested はそうでない
      nested: foo.nested
    })
    
    console.log(foo.nested === bar.nested) // false

    アイデンティティハザードが発生することは一般的に稀です。しかし、安全にアイデンティティハザードを回避しながらこれらの API を適切に利用するには、リアクティビティーの仕組みについてしっかりと理解することが必要です。

effectScope()

エフェクトスコープオブジェクトを作成し、その中に作成されたリアクティブエフェクト(すなわち、computed とウォッチャー)をキャプチャーして、これらのエフェクトを一緒に廃棄できるようにします。この API の詳細な使用例については、対応する RFC を参照してください。

  • ts
    function effectScope(detached?: boolean): EffectScope
    
    interface EffectScope {
      run<T>(fn: () => T): T | undefined // スコープがアクティブでなければ undefined
      stop(): void
    }
  • js
    const scope = effectScope()
    
    scope.run(() => {
      const doubled = computed(() => counter.value * 2)
    
      watch(doubled, () => console.log(doubled.value))
    
      watchEffect(() => console.log('Count: ', doubled.value))
    })
    
    // スコープ内のすべてのエフェクトを破棄
    scope.stop()

getCurrentScope()

現在アクティブなエフェクトスコープがある場合、それを返します。

  • ts
    function getCurrentScope(): EffectScope | undefined

onScopeDispose()

現在アクティブなエフェクトスコープに破棄のコールバックを登録します。このコールバックは、関連するエフェクトスコープが停止されたときに呼び出されます。

各 Vue コンポーネントの setup() 関数はエフェクトスコープでも呼び出されるので、このメソッドは再利用可能なコンポジション関数において、コンポーネントに結合しない onUnmounted の代替として使用できます。

アクティブなエフェクトスコープがない状態でこの関数を呼び出すと、警告が表示されます。バージョン 3.5 以上では、第二引数に true を渡すことで、警告を抑制することができます。

  • ts
    function onScopeDispose(fn: () => void, failSilently?: boolean): void
リアクティビティー API: 上級編が読み込まれました