Skip to content

Instantly share code, notes, and snippets.

@igayamaguchi
Last active Jun 8, 2022
Embed
What would you like to do?
Vue3のprops、slotのTypeScript型検査
<script setup lang="ts">
import StringUnion from './components/StringUnion.vue';
import Emit from './components/Emit.vue';
import Function from './components/Function.vue';
import ComponentInjection from './components/ComponentInjection.vue';
import ComponentInjectionItem from './components/ComponentInjectionItem.vue';
import SlotComponent from './components/SlotComponent.vue';
import SlotProperty from './components/SlotProperty.vue';
const changePropsCorrect = (id: number) => {}
const changePropsIncorrect = (id: string) => {}
const handleChangeCorrect = (id: number) => {
console.log(id)
}
const handleChangeInorrect = (id: string) => {
console.log(id)
}
const handleChangeSubset = () => {
console.log('subset')
}
</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" />
<!-- OK -->
<Function :change="changePropsCorrect" />
<!-- NG -->
<Function :change="changePropsIncorrect" />
<!-- NG:VSCode上はエラーにならないけどvue-tscだとエラーになる -->
<ComponentInjection :item="ComponentInjectionItem" />
</div>
<div>
<h2>Emit</h2>
<!-- OK -->
<Emit @click-event="handleChangeCorrect" />
<!-- NG -->
<Emit @click-event="handleChangeIncorrect" />
<!-- OK:引数が足りていない場合でもエラーにならない -->
<Emit @click-event="handleChangeSubset" />
<!-- OK:定義していないeventを設定していてもエラーにはならない -->
<Emit @invalid-event="handleChangeCorrect" />
</div>
<div>
<h2>Slot</h2>
<SlotComponent>
<template #header>
<div>slot header</div>
</template>
<div>slot</div>
<!-- エラーにはならない -->
<template #invalid>
<div>slot invalid</div>
</template>
</SlotComponent>
<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>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
<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>
<template>
<div>
<h4>ComponentInjectionItem</h4>
<p>{{ id }}</p>
</div>
</template>
<script setup lang="ts">
defineProps<{
id: number
}>()
</script>
<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>
<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>
{
"name": "vue3-ts-example",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"typecheck": "vue-tsc --noEmit"
},
"dependencies": {
"vue": "^3.2.25"
},
"devDependencies": {
"@vitejs/plugin-vue": "^2.2.0",
"@vue/cli-plugin-typescript": "~5.0.0",
"typescript": "^4.5.4",
"vite": "^2.8.0",
"vue-tsc": "^0.29.8"
}
}
<template>
<div>
<h3>Slot</h3>
<slot name="header" />
<slot />
</div>
</template>
<script setup lang="ts">
</script>
<template>
<div>
<h3>SlotProperty</h3>
<slot name="header" value="header" />
<slot :id="1" />
</div>
</template>
<script setup lang="ts">
</script>
<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>
{
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"skipLibCheck": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
{
"compilerOptions": {
"composite": true,
"module": "esnext",
"moduleResolution": "node"
},
"include": ["vite.config.ts"]
}
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()]
})
> npm run typecheck
> vue3-ts-example@0.0.0 typecheck
> vue-tsc --noEmit
src/App.vue:33:20 - error TS2322: Type '"no-theme"' is not assignable to type '"primary" | "secondary"'.
33 <StringUnion theme="no-theme" />
~~~~~
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" />
~~~~~~
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" />
~~~~
src/App.vue:51:27 - error TS2552: Cannot find name 'handleChangeIncorrect'. Did you mean 'handleChangeInorrect'?
51 <Emit @click-event="handleChangeIncorrect" />
~~~~~~~~~~~~~~~~~~~~~
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 }">
~
Found 10 errors in the same file, starting at: src/App.vue:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment