Nuxt3&Vue3アップデートで地味に詰まったところ

最近は細々したタスクを担当しつつ、もっぱらNuxtのアップデート作業に従事しています。

で、色々やっているうちにちょこちょこ知見も溜まってきたのでそれらをまとめてみようと思います。

変数の値

Vueのcomputedよく使うと思います。

例えばVue2までのOptions APIで書くとこんな感じ。

<script>
export default {
  name: 'Hoge',
  data() {
    return {
      hoge: 1
    }
  },
  computed: {
    fuga() {
      return this.hoge * 2;
    },
    piyo() {
      return this.fuga + 1;
    }
  },
  methods: {
    add() {
      this.hoge++;
    }
  }
}
</script>

<template>
  <p>hoge: {{ hoge }}</p>
  <p>fuga: {{ fuga }}</p>
  <p>piyo: {{ piyo }}</p>
  <div>
    <button @click="add">
      add
    </button>
  </div>
</template>

hogeの値を2倍したのがfugafugaに1足したのがpiyoです。

ボタンをクリックするたびhogeがインクリメントしてその他の値も変わるというコンポーネントです。

これをComposition APIで書くとこうなります。

<script setup>
import { ref, computed } from 'vue';
const hoge = ref(1);

const fuga = computed(() => {
  return hoge.value * 2;
});

const piyo = computed(() => {
  return fuga.value + 1;
});

const add = () => {
  hoge.value++;
}
</script>

<template>
  <p>hoge: {{ hoge }}</p>
  <p>fuga: {{ fuga }}</p>
  <p>piyo: {{ piyo }}</p>
  <div>
    <button @click="add">
      add
    </button>
  </div>
</template>

refcomputedRef<T>型のrefオブジェクトを返すので.valueを付けないと値にアクセスできません。

ただ、templateの中では.valueを付けなくても値を取り出せます。最初ここで詰まりました。今でもしっくり来ていません。

加えて、defineProps()で定義するprops.valueを付けなくても<script setup>内で値にアクセスできます。なんで。

リアクティブな値はrefオブジェクトなので.valueが必要、そうでない値はそのままでアクセス可能ということでしょう。

そしてNuxt3のuseAsyncData()の返り値のプロパティであるdataも実はrefオブジェクトです。

型定義を見てみると以下のようになっています。

function useAsyncData(
  handler: (nuxtApp?: NuxtApp) => Promise<DataT>,
  options?: AsyncDataOptions<DataT>
): AsyncData<DataT>
function useAsyncData(
  key: string,
  handler: (nuxtApp?: NuxtApp) => Promise<DataT>,
  options?: AsyncDataOptions<DataT>
): Promise<AsyncData<DataT>>

type AsyncDataOptions<DataT> = {
  server?: boolean
  lazy?: boolean
  default?: () => DataT | Ref<DataT> | null
  transform?: (input: DataT) => DataT
  pick?: string[]
  watch?: WatchSource[]
  immediate?: boolean
}

interface RefreshOptions {
  dedupe?: boolean
}

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

確かにdataの型はRef<DataT | null>となっています。

なのでこれも値を取り出そうとするときは.valueを付けないといけません。

useAsyncData()APIを叩いてデータを取得、computedでそのデータを元に加工して違う値を返す、なんてユースケースはよくあると思いますが、その時もdata.valueとしないといけません。

これドキュメントにも書いておらず、また色んな解説記事も読みましたが書いてある記事は無かったように思います(見逃していたらすみません)。

リクエストパラメータ

Nuxt3ではNuxt2でモジュールとして提供されていたaxiosに代わってofetchが入っています。

useFetch()useAsyncData()も内部ではこのofetchを使った$fetch()が使われています。

で、この$fetch()なんですがリクエストの際のパラメータの設定で詰まりました。

従来のNuxt2でGETリクエストの際はURLSearchParamsを使っていたのですが、それだとクエリパラメータが上手く設定されませんでした。

Githubのissuesを見てみると違う内容の質問ですがこんなのがあったのでこのように対応しました。

export const useCustomFetch = $fetch.create({
  onRequest({ options }) {
    if (options.params) {
      options.params = Object.fromEntries(options.params);
    }
  },
  // 後略
});

useCustomFetchというcomposableを作成しました。

Object.fromEntries()URLSearchParamsからオブジェクトに変換して渡しています。これでGETリクエストはOKです。

POSTリクエストの場合はFormDataを使っていたのですが、こちらはそのままでも使えました。

この違いは何なんでしょうか...。

まとめ

以上、Nuxtアップデートに関連して詰まったところを挙げてみました。

個人的にはやっぱりまだしっくり来ていないところなんですが、とはいえcomposablesにロジックを切り出せるのは良いところだなとも感じるのでメリット・デメリットともにある印象です。

だいぶゴールが見えてきているので最後までしっかりやりきりたいと思います。