Vue 3.3 + Vite で defineProps の分割代入や watch がうまく動作しないときは
結論
vite.config.ts
の plugins
にある vue
のオプションに propsDestructure: true
を追加してください。これによって下記に挙げる問題は解消され、期待した動作になります。
export default defineConfig({
plugins: [
vue({
script: {
propsDestructure: true,
},
}),
],
});
動作確認バージョン
- Vue: 3.3.4
- Vite: 4.4.9
- TypeScript: 5.1.6
背景
Vue 3.3 から defineProps
で受け取ったプロパティを分割代入できるようになりました。props
を宣言する必要がなくなり、リアクティビティを維持しつつもプロパティを直接指定することができるようになりました。
<script setup lang="ts">
const props = defineProps<{
text: string;
important?: boolean;
}>();
</script>
<template>
<div :style="`color: ${props.important ? 'red' : 'black'}`">{{ props.text }}</div>
</template>
<script setup lang="ts">
const { text, important } = defineProps<{
text: string;
important?: boolean;
}>();
</script>
<template>
<div :style="`color: ${important ? 'red' : 'black'}`">{{ text }}</div>
</template>
発生した問題
下記で発生した問題は冒頭の結論で示した、vite.config.ts
に propsDestructure: true
を追加 しない ことで発生するものです。現段階でこれらの問題は警告が出されず、ビルドも成功してしまいます。いずれの問題も実行時に発覚するもので、vite.config.ts
の内容を再確認すべきです。
true
が代入できない
デフォルト値に デフォルト値も分割代入部分に記述できるようになり、withDefaults
を使う必要が無くなりました。
しかしデフォルト値に true
を指定しても実際は false
が代入されてしまいます。もちろん親コンポーネントから :important="true"
のように指定すれば代入ができますし、Boolean 型以外の型であれば正しく動作します。
<script setup lang="ts">
const { text, important = true } = defineProps<{
text: string;
important?: boolean;
}>();
console.info(important); // 親コンポーネントで未指定であると false が出力される
</script>
withDefaults
を使って対応はできる
vite.config.ts
に propsDestructure: true
を指定しなくても、withDefaults
を使ってデフォルト値を正しく指定することはできます。
しかし、withDefaults
でできることは分割代入のデフォルト値指定で達成でき、さらに withDefaults
を deprecated とするPRが提出され(ただし revert されています)、Vue 3.4 以降のどこかで削除されることは確実と思われます。
<script setup lang="ts">
const { text, important } = withDefaults(defineProps<{
text: string;
important?: boolean;
}>(),
{ important: true });
</script>
watch
で変更が検知できない
分割代入をすると watch
(または watchEffect
)を使った変更が検知できなくなります。
<script setup lang="ts">
const { text, important } = defineProps<{
text: string;
important?: boolean;
}>();
watch(() => text, () => { console.info(`text changed: ${text}`); });
</script>
なお、たとえ propsDestructure: true
を指定しても watch
の第1引数にプロパティを直接指定することはできません。公式ガイドのとおり、propsの値を指定する場合は getter を使った方法にしなくてはなりません。これもまた警告がなく、見落としやすいポイントになります。
watch(text, () => { console.info(`text changed: ${text}`); }); // NG
watch(() => text, () => { console.info(`text changed: ${text}`); }); // OK
追記
Vue 3.3.4 現在、以下のような警告が表示されます。存続か廃止か、未だ議論ある機能ですので当該のRFCを確認しておくとよいかもしれません。
[@vue/compiler-sfc] This project is using reactive props destructure, which is an experimental feature. It may receive breaking changes or be removed in the future, so use at your own risk.
To stay updated, follow the RFC at https://github.com/vuejs/rfcs/discussions/502.
Discussion