Open13

Nuxt 2 + Vuetify 2 + Vuex + jest -> Nuxt 3 + Vuetify 3 + Pinia + vitest / storybook利用への移行作業メモ

redamoonredamoon

このスクラップは、来る2023年12月末にVue2 の EOLを迎えるための移行メモとしてのスクラップです。

redamoonredamoon

Aliasの問題

Nuxt3が問題なのかIDEが問題なのか。原因不明。
vueファイルを読み込もうとするとエラーが発生する

Vue: Cannot find module  @/components/AAA  or its corresponding type declarations.
redamoonredamoon

.vue を指定して importすることで解決。
Nuxtの場合、Auto Importが有効であれば指定も不要。

追記メモとして、他のライブラリ vitest とかは unplugin-auto-import/vite を使ってマッピングする。

redamoonredamoon

Vue2で親から子のイベントを実行するときは 子コンポーネントのイベントに this.$ref.hogehoge.eventFunc という形でthisでテンプレートにセットした refを実行していた。

Vue3 からは、 defineExpose({}) を子コンポーネントで関数をセットして、親側で子コンポーネントにセットした ref から参照して実行できる。

Vue2 子のコンポーネント

Child.vue
<template>
  <button type="button" @click="event" />
</template>
<script>
export defalut {
  method: {
    event() {
       console.log('event')
    }
  }
}
</script>

Vue2 親のコンポーネント

Parent.vue
<template>
  <Child ref="refChild" />
</template>
<script>
import Child from './Child'
export defalut {
  components: { Child }
  method: {
    prarentEvent() {
       this.$ref.refChild.event()
    }
  }
}
</script>

Vue3 子のコンポーネント

https://ja.vuejs.org/api/sfc-script-setup#defineexpose

<template>
  <button type="button" @click="event" />
</template>
<script setup lang="ts">
const event = () => console.log('event')
defineExpose({  // defineExpose
  event
})
</script>

Vue3 親のコンポーネント

Parent.vue
<template>
  <Child ref="refChild" />
</template>
<script setup lang="ts">
import Child from './Child.vue'
const refChild = ref()  // refを定義する
const prarentEvent = () => refChild.value.event()  // refChild.valueから子のevent()を呼び出す
</script>
redamoonredamoon

Storybookで、NuxtLinkはMock化する必要がある

Storybookを利用している場合、遭遇率が多いのは NuxtLinkといった Nuxtが固有で持ってる機能の取り扱いです。
Storybookでは識別できないので、preview.tsにNuxtLinkをMock化する必要があります。
具体的には以下のような形で aタグに変換させます。

import type { Preview } from '@storybook/vue3'
import { setup } from '@storybook/vue3'
import { action } from '@storybook/addon-actions'  // addon-actionsはMethodの呼び出しとして利用する

setup((app) => {
  if (app) {
    app.component('NuxtLink', {
      props: {
        to: {
          type: String,
          required: true,
        },
      },
      template: '<a :href="log"><slot></slot></a>',
      setup(props, ctx) {
        const log = action('link target', props.to)
        return { log }
      },
    })
  }
})
redamoonredamoon

Vuetify 3 の VDataTable 変更点

headerの定義変更

  • text -> title に変更
  • value -> key に変更

hide-default-header / hide-defalut-footer廃止?

ヘッダーとフッターを非表示にする機能が VDataTableにはない。
ドキュメント差分

公式に方法書いてないのですが、以下で対応できる。
template #bottomで空で渡すことで非表示になるようです。(検証済み)

<template #bottom></template>

https://stackoverflow.com/questions/76211136/cant-remove-footer-pagination-from-v-data-table-in-vue-3-2

redamoonredamoon

独自に利用した 関数に Options API の thisを利用してた場合のComposition APIの書き方

Options API の this をそのまま 関数に使ってるコードをVue3のComposition APIに書き直すやり方。

Otions API では this を渡すと data や computedといった情報を取り出すことができる。
では、Composition APIの場合は thisがないため変更する必要があります。

具体的なイメージは以下のようなOptions API のコードをComposition APIに書き直した。
以下のコードはmessageという関数に thisを渡し関数の中で必要なプロパティを取り出して利用している。

Otions API

<script lang="ts">
import { defineComponent } from "vue";
import messages from "./messages";
// Options API
export default defineComponent({
  data() {
    return {
      age: 18,
      name: "vite",
    };
  },
  computed: {
    getMessage() {
      return messages(this, "options API");  // <-- ここでthisを渡すとcomputedやdataの情報を代入できる
    },
  },
});
</script>
interface Option {
  name: string;
  age: number;
}
const messages = (option: Option, type: string) => {
  console.log(option, type);
  const { name, age } = option;
  return `Hello ${name}, your age is ${age}`;
};
export default messages;

Composition API

Composition APIの場合、dataとして定義してた ageやnameといった情報を refreactive といった形で定義する必要がある。
それぞれの変数になるため、いままでの関数の呼び出しでthisを利用できない。
呼び出す前に必要な変数をまとめる必要です。

<script setup lang="ts">
import { ref, computed } from "vue";
import messages from "./messages";

const age = ref(18);
const name = ref("vite");

const getMessage = computed(() => {
  const options = { name: name.value, age: age.value }; // <-- thisだった部分を一時的な変数にまとめる
  return messages(options, "composition API"); // <-- ここでthisの代わりにoptionsに変更する
});
</script>

<template>
  <div>
    <h4>Composition API</h4>
    <p>{{ getMessage }}</p>
  </div>
</template>

codesandbox

https://codesandbox.io/p/devbox/vueoptionsapi-compositionapi-5yjwvq?embed=1&file=%2Fsrc%2Fcomponents%2FSampleProfileCompositionAPI.vue&showConsole=true&layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522clteesv3a00073b6hatgwa3vd%2522%252C%2522sizes%2522%253A%255B70%252C30%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522clteesv3a00023b6hiycx77w4%2522%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522clteesv3a00043b6h6lx6wnq2%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522clteesv3a00063b6hq0h1kf5p%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%252C%2522sizes%2522%253A%255B50%252C50%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522clteesv3a00023b6hiycx77w4%2522%253A%257B%2522id%2522%253A%2522clteesv3a00023b6hiycx77w4%2522%252C%2522tabs%2522%253A%255B%255D%257D%252C%2522clteesv3a00063b6hq0h1kf5p%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clteesv3a00053b6hkppoph5c%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TASK_PORT%2522%252C%2522taskId%2522%253A%2522pnpm%2520run%2520dev%2522%252C%2522port%2522%253A5173%252C%2522path%2522%253A%2522%252F%2522%257D%255D%252C%2522id%2522%253A%2522clteesv3a00063b6hq0h1kf5p%2522%252C%2522activeTabId%2522%253A%2522clteesv3a00053b6hkppoph5c%2522%257D%252C%2522clteesv3a00043b6h6lx6wnq2%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clteesv3a00033b6hes0tg5w1%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522pnpm%2520run%2520dev%2522%257D%255D%252C%2522id%2522%253A%2522clteesv3a00043b6h6lx6wnq2%2522%252C%2522activeTabId%2522%253A%2522clteesv3a00033b6hes0tg5w1%2522%257D%257D%252C%2522showDevtools%2522%253Atrue%252C%2522showShells%2522%253Atrue%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A15%257D

redamoonredamoon

Storybookでイベントをロギングする

redamoonredamoon

ロギングするためには、必ず on を接頭辞としてつける必要がある。
例えばupdate:model-valueの場合は以下のようにargTypesを設定する必要がある。
onUpdate:modelValue

  argTypes: {
    'onUpdate:modelValue': { action: 'modelValue' },
  },
redamoonredamoon

Nuxt 3 で storybookを起動させると useRuntimeConfig でエラーが発生して autoimportされる関数が使えない。
対応方法は、以下のようの .storybook/preview-body.html を作成する
publicで設定している。nuxtのapiBaseも同様に設定することでエラーは解消できる。

<script>
  const useRuntimeConfig = () => {
    return {
      isDevelopment: true,
      public : {
        apiBase: 'http://localhost:XXXXX'
      }
    }
  }
</script>
redamoonredamoon

VDateTable の expandで開閉する機能で 行自体をクリックする場合、 v2では @click:row にイベントをつけてた。
v3でも @click:row でイベントを貼って idなりを取得できるが、 props-expand-on-click という propsが定義できるようになった。
これによって、ただの行をクリックするためのイベントを貼らずに props-expand-on-click にboolean渡せば行リックが実装できるようになった。

https://vuetifyjs.com/en/api/v-data-table/#props-expand-on-click

id を設定することで、rowの位置を確認できる。