TL, DR
コンポーネントのpropsにより厳格な型を指定するためのライブラリ vue-strict-prop をつくりました。
意見などもらえると喜びます。
問題
Vue 2.5からTypeScript向けの型定義が大きく改善され、コンポーネントのpropsの型もコンポーネントオプションを元に推論してくれるようになりました。
例えばこんな感じ
export default Vue.extend({
name: "ExampleComponent",
template: "...",
props: {
// titleの型はstringとみなされる
title: String,
// itemsの型はany[]とみなされる
items: { type: Array, required: true },
// filterの型は (...args: any[]) => any とみなされる
filter: { type: Function, required: true },
// directionの型はstringとみなされる
direction: {
type: String,
default: "horizontal",
validator: v => v === "horizontal" || v === "vertical"
}
}
});
しかし、この推論結果について、以下のような点が気になります。
-
title
にはrequired
もdefault
も指定されていないので、値が入らないケースがある。
つまり、本当はstring
ではなくてstring | undefined
になってほしい -
items
は配列だけど、できれば要素の型も指定したい -
filter
は関数だけど、正確なシグネチャ(例えば (value: string) => boolean とか)を指定したい -
direction
は、string
ではなく"horizontal" | "vertical"
になってほしい
vue-strict-prop
これを改善するべく、vue-strict-prop
というちょっとしたライブラリを作りました。
これを使うと、上のコードは下のようになります。
import p from "vue-strict-prop"
export default Vue.extend({
name: "ExampleComponent",
template: "...",
props: {
// titleの型はstring | undefinedとみなされる
title: p(String).optional,
// itemsの型はstring[]とみなされる
items: p.ofArray<string>().required,
// filterの型は (value: string) => boolean とみなされる
filter: p.ofFunction<(value: string) => boolean>().required,
// directionの型は"horizontal" | "vertical"とみなされる
direction: p.ofStringLiterals("horizontal", "vertical").default("horizontal")
}
});
使い方
基本のパターン。型を指定した後、optional
、required
、default
のどれかを指定します。
optional
、required
、default
のどれかで終わるのは、以下すべてのパターンで共通
import p from "vue-strict-props";
p(TYPE).optional // { type: TYPE }
p(TYPE).required // { type: TYPE, required: true }
p(TYPE).default(VALUE) // { type: TYPE, default: VALUE }
validator を指定したい場合はこう
p(String).validator(v => v.length < 3).required
複数の型を指定するパターン。今のところ指定できるのは5つまで
p(TYPE1, TYPE2).optional
ArrayやFunctionなどに詳細な型をつけるために、それぞれメソッドが用意されています。
p.ofArray<string>().required
// Array ではなく ReadonlyArrayを使う場合はofRoArrayで
p.ofRoArray<string>().required
p.ofFunction<() => void>().required
p.ofObject<{ foo: string }>().required
// 型が"foo" | "bar"になるだけでなく、`value in ("foo", "bar")` 相当のvalidatorも登録される
p.ofStringLiterals("foo", "bar").required
型指定なしも
p.ofAny().required
or
を使った条件の結合
6つ以上の型を指定したい場合(ないと思うけど)も or
を使います
p(String).or.ofFunction<() => string>().required
p(TYPE1, TYPE2, TYPE3, TYPE4, TYPE5).or(TYPE6, TYPE7).required