Slide 1

Slide 1 text

Vue SFCのtemplateで TypeScriptの型を活用しよう ストックマーク株式会社 tsukkee 2024/10/19 Vue Fes Japan 2024

Slide 2

Slide 2 text

2 築谷 喬之 (つきたに たかゆき) 所属 ストックマーク株式会社 役割 自然言語処理を活用したBtoB SaaSの開発 開発チームのマネージメント tsukkee (つっきー) 自己紹介 エディタ Vim 趣味 Vim上でVueのLanguage Serverを動かすこと

Slide 3

Slide 3 text

VueでもTypeScriptの活用に注目が集まる! https://speakerdeck.com/tsukuha/sabisukai-fa-niok eruvue3totypescriptnoqin-he-xing-nituite https://speakerdeck.com/minako__ph/xian-dai-nov uetotypescript-xing-an-quan-nohuo-yong-shu Composition APIやscript setupによって型安全性が高まっている はじめに

Slide 4

Slide 4 text

SFCのtemplateでも型を活用していますか? defineProps<{ input<: string }>();
{{ input }}
ここではstring | undefinedだけど… ここではstringになる! v-ifでTypeScriptのif文と 同様のnarrowingが行われ… はじめに

Slide 5

Slide 5 text

Vue Language Toolsがtemplateの型もサポート Volar.jsの詳細は昨年のmizdraさんの発 表もご参照ください! https://speakerdeck.com/mizdra/vue-language- server-karasheng-mareta-volar-dot-js-to-soreg ami-meruke-neng-xing ● Vue Language Toolsとは、VS CodeのVue Extension(Vue - Official)や vue-tsc、VueのLanguage Serverなどのツール群 ● 基盤であるVolar.jsの機能を使って、templateをTypeScriptに変換し tscに連携し型チェック Vue Language Tools

Slide 6

Slide 6 text

Volar Labs VSCode上で動作するVolarのデバッグツールで、 side-by-sideでSFC のtemplateがTypeScriptに変換される様子も確認できる https://volarjs.dev/core-concepts/volar-labs/ Vue Language Tools

Slide 7

Slide 7 text

v-if TypeScriptのif文に変換される (<_VLSではじまる内部変数は今回は雰囲気で感じ取ってください^^;)
{{ input }}
if (<_VLS_ctx.input) { <_VLS_elementAsFunction( <_VLS_intrinsicElements.div, <_VLS_intrinsicElements.div ) ({}); ( <_VLS_ctx.input ); } 一部抜粋 & 整形 template → TypeScript

Slide 8

Slide 8 text

v-for TypeScriptのfor文に変換される
{{ item }}
for (const [item] of <_VLS_getVForSourceType((<_VLS_ctx.items)!)) { <_VLS_elementAsFunction( <_VLS_intrinsicElements.div, <_VLS_intrinsicElements.div ) ({key: ((item)), }); ( item ); [items,]; } 一部抜粋 & 整形 template → TypeScript

Slide 9

Slide 9 text

コンポーネント呼び出し defineProps<{ value: number }>(); defineEmits<{ update: [value: number] }>(); 子コンポーネント 呼び出し側(抜粋) const <_VLS_0 = <_VLS_asFunctionalComponent( ChildComponent, new ChildComponent({ <<.{ 'onUpdate': {} as any }, value: ((<_VLS_ctx.count)), }) ); const <_VLS_1 = <_VLS_0( {<<.{ 'onUpdate': {} as any }, value: ((<_VLS_ctx.count)), }, <<.<_VLS_functionalComponentArgsRest(<_VLS_0) ); let <_VLS_5!: <_VLS_FunctionalComponentProps; const <_VLS_6: Record & ( 略 ) = { onUpdate: (<_VLS_ctx.handleUpdate) }; 一部抜粋 & 整形 大雑把には、propsとemitsを引数とする関数呼び出しに変換される template → TypeScript

Slide 10

Slide 10 text

template上でExhaustive checkできる! defineProps<{ check: never }>(); never型を受け取るだけのExhaustiveCheck.vueを用意して… template上でExhaustive check

Slide 11

Slide 11 text

import ExhaustiveCheck from './ExhaustiveCheck.vue'; type Card = | { type: 'A'; id: number; name: string; } | { type: 'B'; id: number; label: string; } | { type: 'C'; id: number; title: string; }; const cards: Card[] = [ <* 略 </ ];
{{ card.name }}
{{ card.label }}
{{ card.title }}
template上でExhaustive check v-if…v-else-if…v-elseの最後に置いておくと… ココ!

Slide 12

Slide 12 text

template上でExhaustive check たとえば、type: 'D'を追加したとき、ちゃんと型エラーになる!

Slide 13

Slide 13 text

スロット (1/2) スロット定義からスロット引数の型が推論される import { ref } from 'vue'; const value = ref(0);
var <_VLS_0 = { slotParam: ((<_VLS_ctx.value)), }; 略 var <_VLS_slots!:{ option?(_: typeof <_VLS_0): any, }; 一部抜粋 & 整形 optionスロットは number型の slotParamを スロット引数に取る template → TypeScript

Slide 14

Slide 14 text

スロット (2/2) defineSlotsで明示的に型を指定することもできる import { ref } from 'vue'; defineSlots<{ option: { slotParam: number } }>(); const value = ref(0);
const <_VLS_slots = defineSlots<{ option: { slotParam: number } }>() 略 <_VLS_normalizeSlot(<_VLS_slots['option'])<.({ slotParam: ((<_VLS_ctx.value)), }); 一部抜粋 & 整形 template → TypeScript

Slide 15

Slide 15 text

const props = defineProps<{ tabs: T }>(); defineSlots<{ [K in keyof T as K extends string ? `tab-${K}` : never]: (_: { value: T[K] }) <> unknown }>();
genericも使うと動的なスコープ付きスロットの型付けもできる! defineSlots & generic genericでtabsの 型を受け取って、 `tab-${K}`という 命名規則でスロットの 型に展開し、 template上に反映する 例: タブコンポーネントを作りたい (けど、タブ切り替えロジックなどは省略 )

Slide 16

Slide 16 text

こんな感じで使える! import MyTabs from './MyTabs.vue'; const tabs = { counter: { title: 'count', count: 1234 }, image: { label: 'JPG image', image: 'image.jpg' }, };

{{ value.title }}

{{ value.count }}
{{ value.label }}
{{ value.label }}
value: { title: string; count: number; } 存在しない プロパティは型エラー value: { label: string; image: string; } 存在しない スロットも型エラー 「tab-${キー名}」 というスコープ付き スロットを生成 defineSlots & generic

Slide 17

Slide 17 text

templateでもTypeScriptの力を活用できる!! ● SFCのtemplate部分はTypeScriptに変換されてから型チェックされる ○ v-ifはif文、v-forはfor文、コンポーネント呼び出しは 関数呼び出し、etc. ○ スロット引数は推論されるし、defineSlotsで明示することもできる ● template上でもTypeScriptのいつものテクニックが使える ○ Narrowing、Exhaustive check、etc. ○ コンポーネントのgenericも使って型パズルもできる! まとめ