Vue3において、どのくらいVueのtemplateで型をチェックできるようになったのか確認する
https://igatea.hatenablog.com/entry/2022/05/01/194916 と同じように npm init vite
で生成したVue3テンプレートで検証をしていく
VSCode上でVolarを使ってエディタ上でも型検査が効きエラーメッセージが出るか、vue-tsc --noEmit
を叩いて型検査が行えるかを確認する
検証項目
- Props
- String union
- Function
- Component injection
- Emit
- Slot
- Slot property
基本的にVueのSFCのtemplateとscript setupで検証をする
JSXやoption api、option apiの中でのsetupでは検証していない
検証コード https://gist.github.com/igayamaguchi/650726b9e84ce9fb14817657a278c857
結論
先に結論
検査項目 | 型 |
---|---|
Props String union | 効く |
Props Function | 効く |
Props Component injection | VSCode上ではエラーにならないが効く。vue-tscではエラーになる |
Emit | 効く。ただし部分集合のようになっているようで引数が足りていないものはエラーにならない。未定義のイベントを設定してもエラーにならない |
Slot | 効かない |
Slot Property | VSCode上では聞かない、vue-tscでは効く |
なぜかVSCodeとvue-tscで若干差がある。原因は不明
Props
String union
<template> <div> <h3>StringUnion</h3> <p>{{ theme }}</p> </div> </template> <script lang="ts"> export const themes = ['primary', 'secondary'] as const export type Theme = typeof themes[number] </script> <script setup lang="ts"> defineProps<{ theme: Theme }>() </script>
<script setup lang="ts"> import StringUnion from './components/StringUnion.vue'; </script> <template> <div> <h1>App</h1> <div> <h2>Props</h2> <!-- OK --> <StringUnion theme="primary" /> <!-- NG --> <StringUnion theme="no-theme" /> <!-- OK:定義していないpropsを設定していてもエラーにはならない --> <StringUnion theme="primary" invalid-value="primary" /> </div> </div> </template>
vue-tsc
src/App.vue:33:20 - error TS2322: Type '"no-theme"' is not assignable to type '"primary" | "secondary"'. 33 <StringUnion theme="no-theme" /> ~~~~~
VSCode上も、vue-tscでもしっかりString unionも型が効いた
Function
<template> <div> <h3>Function</h3> </div> </template> <script lang="ts"> export type ChangeFn = (id: number) => void </script> <script setup lang="ts"> defineProps<{ change: ChangeFn }>() </script>
<script setup lang="ts"> import Function from './components/Function.vue'; const changePropsCorrect = (id: number) => {} const changePropsIncorrect = (id: string) => {} </script> <template> <div> <h1>App</h1> <div> <h2>Props</h2> <!-- OK --> <Function :change="changePropsCorrect" /> <!-- NG --> <Function :change="changePropsIncorrect" /> </div> </template>
vue-tsc
src/App.vue:40:18 - error TS2322: Type '(id: string) => void' is not assignable to type 'ChangeFn'. Types of parameters 'id' and 'id' are incompatible. Type 'number' is not assignable to type 'string'. 40 <Function :change="changePropsIncorrect" /> ~~~~~~
これも正しく型の検査がされている
Component injection
子コンポーネント
ComponentInjection.vue
<template> <div> <h3>ComponentInjection</h3> <Component :is="item" /> </div> </template> <script setup lang="ts"> import { DefineComponent } from 'vue'; import ComponentInjectionItem from './ComponentInjectionItem.vue'; defineProps<{ item: typeof ComponentInjectionItem, }>() </script>
Propsに指定するコンポーネント
ComponentInjectionItem.vue
<template> <div> <h4>ComponentInjectionItem</h4> <p>{{ id }}</p> </div> </template> <script setup lang="ts"> defineProps<{ id: number }>() </script>
<script setup lang="ts"> import ComponentInjection from './components/ComponentInjection.vue'; import ComponentInjectionItem from './components/ComponentInjectionItem.vue'; </script> <template> <div> <h1>App</h1> <div> <h2>Props</h2> <!-- NG:VSCode上はエラーにならないけどvue-tscだとエラーになる --> <ComponentInjection :item="ComponentInjectionItem" /> </div> </div> </template>
vue-tsc
src/App.vue:43:28 - error TS2322: Type 'DefineComponent<__VLS_DefinePropsToOptions<{ id: number; }>, {}, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, ... 4 more ..., {}>' is not assignable to type 'ComponentPublicInstanceConstructor<{ $: ComponentInternalInstance; $data: {}; $props: Partial<{}> & Omit<Readonly<ExtractPropTypes<__VLS_DefinePropsToOptions<{ id: number; }>>> & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>; ... 10 more ...; $watch(source: string | Function, cb: Function, option...'. Type 'ComponentPublicInstanceConstructor<{ $: ComponentInternalInstance; $data: {}; $props: Partial<{}> & Omit<Readonly<ExtractPropTypes<__VLS_DefinePropsToOptions<{ id: number; }>>> & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>; ... 10 more ...; $watch(source: string | Function, cb: Function, option...' is missing the following properties from type '{ __VLS_raw: DefineComponent<__VLS_DefinePropsToOptions<{ id: number; }>, {}, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, ... 4 more ..., {}>; __VLS_options: { ...; }; __VLS_slots: {}; }': __VLS_raw, __VLS_options, __VLS_slots 43 <ComponentInjection :item="ComponentInjectionItem" /> ~~~~
Emit
<template> <div @click="handleClick"> <h3>Emit</h3> </div> </template> <script setup lang="ts"> const emits = defineEmits<{ (e: 'click-event', id: number): void }>() function handleClick() { emits('click-event', 1) } </script>
<script setup lang="ts"> import Emit from './components/Emit.vue'; const handleChangeCorrect = (id: number) => { console.log(id) } const handleChangeInorrect = (id: string) => { console.log(id) } const handleChangeNotEnough = () => { console.log('not enough') } </script> <template> <div> <h1>App</h1> <div> <h2>Emit</h2> <!-- OK --> <Emit @click-event="handleChangeCorrect" /> <!-- NG --> <Emit @click-event="handleChangeIncorrect" /> <!-- OK:引数が足りていない場合でもエラーにならない --> <Emit @click-event="handleChangeNotEnough" /> <!-- OK:定義していないeventを設定していてもエラーにはならない --> <Emit @invalid-event="handleChangeCorrect" /> </div> </div> </template>
vue-tsc
src/App.vue:51:27 - error TS2552: Cannot find name 'handleChangeIncorrect'. Did you mean 'handleChangeInorrect'? 51 <Emit @click-event="handleChangeIncorrect" /> ~~~~~~~~~~~~~~~~~~~~~
Slot
<template> <div> <h3>Slot</h3> <slot name="header" /> <slot /> </div> </template> <script setup lang="ts"> </script>
<template> <div> <h3>Slot</h3> <slot name="header" /> <slot /> </div> </template> <script setup lang="ts"> </script>
vue-tsc
<script setup lang="ts"> import SlotComponent from './components/SlotComponent.vue'; </script> <template> <div> <h1>App</h1> <div> <h2>Slot</h2> <SlotComponent> <template #header> <div>slot header</div> </template> <div>slot</div> <!-- エラーにはならない --> <template #invalid> <div>slot invalid</div> </template> </SlotComponent> </div> </div> </template>
Slot property
<template> <div> <h3>SlotProperty</h3> <slot name="header" value="header" /> <slot :id="1" /> </div> </template> <script setup lang="ts"> </script>
<script setup lang="ts"> import SlotProperty from './components/SlotProperty.vue'; </script> <template> <div> <h1>App</h1> <div> <h2>Slot</h2> <SlotProperty v-slot="slotProps"> <!-- OK --> <div>slot {{ slotProps.id }}</div> <!-- NG --> <div>slot {{ slotProps.x }}</div> </SlotProperty> <!-- slot properyに未定義のxはエラーに --> <SlotProperty v-slot="{ id, x }"> <div>slot {{ id }}</div> <div>slot {{ x }}</div> </SlotProperty> <SlotProperty> <template #header="headerProps"> <!-- OK --> <div>header {{ headerProps.value }}</div> <!-- NG --> <div>header {{ headerProps.x }}</div> </template> <template v-slot="slotProps"> <!-- OK --> <div>slot {{ slotProps.id }}</div> <!-- NG --> <div>slot {{ slotProps.x }}</div> </template> </SlotProperty> <SlotProperty> <!-- slot properyに未定義のxはエラーに --> <template #header="{ value, x }"> <div>header {{ value }}</div> <div>header {{ x }}</div> </template> <!-- slot properyに未定義のxはエラーに --> <template v-slot="{ id, x }"> <div>slot {{ id }}</div> <div>slot {{ x }}</div> </template> </SlotProperty> </div> </div> </template>
vue-tsc
src/App.vue:76:32 - error TS2339: Property 'x' does not exist on type '{ id: number; }'. 76 <div>slot {{ slotProps.x }}</div> ~ src/App.vue:79:35 - error TS2339: Property 'x' does not exist on type '{ id: number; }'. 79 <SlotProperty v-slot="{ id, x }"> ~ src/App.vue:89:38 - error TS2339: Property 'x' does not exist on type '{ value: string; }'. 89 <div>header {{ headerProps.x }}</div> ~ src/App.vue:95:34 - error TS2339: Property 'x' does not exist on type '{ id: number; }'. 95 <div>slot {{ slotProps.x }}</div> ~ src/App.vue:100:37 - error TS2339: Property 'x' does not exist on type '{ value: string; }'. 100 <template #header="{ value, x }"> ~ src/App.vue:105:33 - error TS2339: Property 'x' does not exist on type '{ id: number; }'. 105 <template v-slot="{ id, x }"> ~