useAsyncData と reactivity

Published at: 2022/12/4


useAsyncData('key', asyncHandlerFunc)

前提

Value に対する Reactivity

前提の記事で共有した通り、 useAsyncData は SPA としてシングルトン的なキャッシュ置き場を持っていて、そこにすべての useAsyncData の key とそれに対応する結果 (useAsyncData を await して帰ってくるオブジェクト) を保持する。

type AsyncData<DataT, ErrorT> = {
  data: Ref<DataT | null>
  pending: Ref<boolean>
  execute: () => Promise<void>
  refresh: () => Promise<void>
  error: Ref<ErrorT | null>
}

useAsyncData の結果は、 datapending のプロパティが Ref であることから分かるように、 クライアント側でその呼び出している async 関数(handler) の Promise によって、非同期に更新されている構成となっている。

実は、この AsyncData のオブジェクトは、 useAsyncData 関数の await する前の直の戻り値としても利用可能になっている。 というのも、

https://github.com/nuxt/framework/blob/dd56d36e3ea012d635daf22677086b8218f6e089/packages/nuxt/src/app/composables/asyncData.ts#L235

ここでの処理によって、 AsyncData オブジェクトの各プロパティは、 AsyncData を返す Promise オブジェクトへとマージされている。

これを利用して、ページ遷移の際などクライアント側で useAsyncData の結果を待たずにページを表示することが可能となる。 (SSR の場合は、おそらくすべての asyncData Map の promise が resolve するのを待ってから SSR するので、これをやったからといってページの 初期の html の結果がローディング状態になる、ということはない模様。 )

<script setup lang="ts">
  const { data, pending } = useAsyncData('foo', async () => {
    // データの取得
  })
</script>

<template>
  <div>
    <!-- data や pending を使って条件分岐してテンプレートを組み立て -->
  </div>
</template>

reactivity まわりのオプション解説

  • lazy: true の場合、 useAsyncData の戻り値の Promise は、 await したとしても handler の発火やその結果の await とは関係なしに、ただただその時の時点の AsyncData が resolve される。
    • onBeforeMount で実際に handler の実行は開始される。
  • immediate: false を指定すると、手動で refresh / execute (両者の実態は同じもの) を呼ばないかぎり handler が呼ばれない。
  • watch: Vue の watch 機構の callback に refresh の呼び出しを bind するような処理を行う。

Key に対する reactivity は存在しない

useAsyncData は、その設計思想として key に対して非同期データ取得の promise をデータキャッシュ的に紐付けを行うことで実現されている。 なので、 key が reactive に変化していくような使い方は想定されておらず、さらにはそれを無理矢理やろうとしてもおそらく上手くいかない。

ただ、一方で例えば routing のパラメーターをキーの一部に埋め込んだりしたい場合には、どういう実装をするのが良いのか、という話になる。

Nuxt 3 においては、 NuxtLayoutNuxtPage はそれぞれインスタンス化される際に、 key 属性が指定される。 NuxtLayout はレイアウト自身の名前が指定され、 NuxtPage は url のパスが指定される。 これにより、ページが変更される場合には、漏れなく PageComponent はこれまでのものが破棄され、新しい route パラメータを持った page コンポーネントが生成されなおすことになる。 これにより、少なくとも url に対して一意になるように useAsyncData のキーを指定しておけば、 reactivity 問題を意識する必要はあまりなくなる。

<template>
  <div>
    <!-- data を利用した何かしら -->
  </div>
</template>

<script setup lang="ts">
  const route = useRoute()
  const slug = route.params.slug
  const { data } = await useAsyncData(`some-path/${slug}`, () => {
    // slug を利用した処理な何かしら
  })
</script>

url に紐付かないような場合であっても、同様に、動的な useAsyncData のキーを指定する場合には、インスタンス化された component が生きている間に reactivity によって途中で key のあるべき値が変わらないように実装することが、おそらく useAsyncData 的には想定された利用ユースケースだと考えられる。

参考

framework/asyncData.ts at main · nuxt/framework · GitHub

The Intuitive Web Framework, based on Vue 3. Contribute to nuxt/framework development by creating an account on GitHub.

github.com

間違い・補足などあれば

twitter にて連絡いただけると幸いです。


Tags: nuxt3nuxt

Related Articles