こんにちは、ブロックチェーンチームの id:d-kimsuon です
Vue2 では TypeScript がサポートされており、公式の TypeScript のサポートのドキュメント に従うことで TypeScript で書いていくことができます
しかし、素直に書いていくと any 型になってしまったり、実際とは異なる型付けになってしまうポイントがあります
この記事では TypeScript で Vue2 (Option API) を書くときに型を厳格にしやすい書き方等を紹介します
- 省略可能な props には明示的に PropType<型 | undefined> する
- props の型でリテラルに絞っている時、 default には as const をつける
- data の型を as で上書きせず、型注釈を書く
- asyncData の型は推論ではなく Promise<Partial<Data>> を使う
- watch には型注釈を明示する
- ref からメソッドを呼ぶ
- 最後に
省略可能な props には明示的に PropType<型 | undefined>
する
Vue の props では required: false
または default に値を設定することで、省略可能なプロパティを宣言することができます
型安全な例
PropType
で明示的に型を指定することで、型安全に参照することができます
Vue.extend({ props: { foo: { type: String as PropType<string | undefined>, required: false }, } })
型安全でない例
次のような素直な実装でも省略可能な props は実現できますが、型付けが厳格でなくなります
fooは、いずれも string | undefined
に型推論されてほしいですが、 string
に推論されてしまいます
Vue.extend({ props: { foo: { type: String, required: false }, } })
Vue.extend({ props: { foo: { type: String, default: undefined }, } })
props の型でリテラルに絞っている時、 default には as const をつける
PropType<'ok' | 'ng'>
等で受け取る型をリテラルで絞っていても default 値が指定されていると string に丸められてしまいます
as const をデフォルト値につけることで PropType の型でちゃんと推論してくれるようになります
型安全な例
Vue.extend({ props: { foo: { type: String as PropType<'x' | 'y' | 'z'>, default: 'x' as const // これがないと string に推論される } } })
型安全でない例
Vue.extend({ props: { foo: { type: String as PropType<'x' | 'y' | 'z'>, default: 'x' // as constがないため、string に推論される } } })
data の型を as で上書きせず、型注釈を書く
data はミュータブルなデータなので初期値からの推論が適切でないときがあります
そういうとき、as で型を上書きする手法が使われがちですが型と値がズレる原因になります
型安全な例
data メソッドの戻り値の型を書くことで安全に data の型を宣言できます
Vue.extend({ data(): { foo: { hoge: string } } { return { foo: { hoge: "hello" } } } })
型安全でない例
Vue.extend({ data() { return { foo: null as null | { hoge: string } } } })
as だと型注釈とは異なり、型情報を上書きします。値をその型の変数に束縛できるかを緩くしか検証しないので型と値が一致しなくなる原因になります
例えば以下は型エラーがでない、型と値がズレる例です
// asで型を書くダメな例 Vue.extend({ data() { return { foo: {} as { hoge: string } } } })
型注釈で書いていれば {}
は 型 { hoge: string }
に割り当てることはできませんが、as で書いていたために代入できてしまっています
既存のコンポーネントに data を追加する際に型注釈が書いてないと as で対応しがちなのかなと思っているので、最初から型注釈を書いておくのが良いのかなと思っています
asyncData の型は推論ではなく Promise<Partial<Data>>
を使う
※ nuxt の話題で、v2.16.0で修正されている のでそれより古いバージョンの前提です
asyncData にだけ書いたデータは this.serverData
が生えず型安全に参照することができません
ですので、data の型に SSR から取得するデータの型も初期値とセットで宣言しておくのがオススメです
型安全な例
// ローカルステートの型を初期化状態とセットで宣言して type Data = { clientData: string, serverData: string | undefined /* 未ロードを undefined にする */, } Vue.extend({ // asyncData は Partial<Data> に注釈をつける asyncData: async (): Promise<Partial<Data>> => { return { serverData: 'hogehoge' } }, data(): Data { return { clientData: 'hello', serverData: undefined // 初期化 } } })
型安全でない例
type Data = { clientData: string, } Vue.extend({ asyncData: async () => { return { serverData: 'hogehoge' } }, data(): Data { return { clientData: 'hello', } }, methods: { foo() { console.log(this.serverData) // 型エラー } } })
watch には型注釈を明示する
watch 対象のプロパティをの型を推論してほしいですが、実際には入ってくれません
型安全な例
やや手間ですが型注釈を明示してあげることで、型安全に利用できます
Vue.extend({ props: { foo: String }, watch: { hoge(nextValue: string, prevValue: string): void { // 明示する } } })
型安全でない例
Vue.extend({ props: { hoge: String }, watch: { hoge(nextValue, prevValue): void { // nextValue と prevValue は string に推論されてほしいけど any になる } } })
ref からメソッドを呼ぶ
ref を使って子コンポーネントのメソッドを呼び出すとき、型安全にメソッドを呼ぶことはできません
子コンポーネントのメソッドを呼ぶこと自体避けたほうが良いかもしれませんが、止むをえず呼ぶ場合は以下のような型の Utility を準備しておくことで型安全に呼び出すことができます
type ComponentMethods<Comp> = Comp extends ExtendedVue< Vue, unknown, infer I, unknown, unknown > ? I : never; type TypedVueRef<Comp extends VueConstructor> = Vue & ComponentMethods<Comp>;
以下のように利用します
const ref = this.$refs['v-comp'] as unknown as TypedVueRef<typeof VComp>
最後に
この記事ではVue2系で型安全性を守りやすい書き方を紹介しました
Vue で型が厳格にならず困っている方はぜひお試しください!