ソース
こんだけ。
const VuePropsPlugin = {
install(vue, options) {
vue.mixin({
beforeCreate: function() { this.$props = this; }
});
}
}
これは何?
vue.jsのコンポーネントに、 $props
というインスタンスプロパティを生やすためのplugin
コンポーネントのprop(例えば foo
)に this.foo
だけでなく this.$props.foo
という形でもアクセスできるようになる。
とりあえず、そういうプラグインがあった場合にどの程度うれしい試してみる目的のものなので、ウルトラ雑実装。($props
に this
を突っ込んでいるだけなので、propどころかあらゆるインスタンスメンバに $props
経由でアクセスできる)
ちゃんと作るなら $options.props
の内容をもとにラッパーオブジェクトを生成する的な実装になると思う。
TypeScript >= 2.1と一緒に使うのを想定している。
それで何がうれしいの?
現状
現状、TypeScript でvueのコンポーネントを書くときには、クラススタイルで書いて、デコレータでコンポーネント化する方法が主流。
@component({
props: { foo: { type: String }, bar: { type: String } }
})
class MyComponent extends Vue {
foo: string;
bar: string;
}
この時、propsの定義をデコレータの引数に渡しつつ、クラスのメンバとしても定義しなければならず、定義が分散してしまうのが悩みの種であり、それを解決するために、追加のデコレータを用意していたりする。 1
@component({
/* propsの定義は@propで指定した内容から自動的に構築されるので、ここではもう指定しなくていい */
})
class MyComponent extends Vue {
@prop({ type: String }) foo: string;
@prop({ type: String }) bar: string;
}
このコンポーネントを親コンポーネントの render
内で生成する場合などを考えると propsをインターフェイスにしておいた方が都合がよく、この方法はそのインターフェイスとの親和性が高いというメリットもある。
// MyComponentのProps
interface MyProps { foo: string, bar: string }
@component({ ... })
class MyComponent extends Vue implements MyProps {
// MyPropsをimplementsすれば、foo, barの名前と型に間違いがないことは検証できる。
@prop({ type: String }) foo: string;
@prop({ type: String }) bar: string;
}
TypeScript 2.1 以後
TypeScript 2.1で導入されたMapped Typeによって、安全に props
の定義を書けるようになった。
// こういう型定義をあらかじめ用意しておく
type Props<T> = { [K in keyof T]: Vue.PropOptions | String | Number | ... };
interface MyProps { foo: string, bar: string }
@component({
props: <Props<MyProps>> { // foo, barをtypoしてたり過不足があったらコンパイルエラーになる
foo: { type: String },
bar: { type: String }
}
})
class MyComponent extends Vue { /* 略 */ }
ただ、foo
, bar
をクラスのメンバとして定義しないといけないようだと結局また二重定義になってしまってありがたみがない、というところで、ようやくこのプラグインの出番。
詳細(というほどでもないけど)は以下のコードを参照。
interface MyProps { foo: string, bar: string }
@component({
props: <Props<MyProps>> { // foo, barをtypoしてたり過不足があったらコンパイルエラーになる
foo: { type: String },
bar: { type: String }
}
})
class MyComponent extends Vue {
// foo, barを個別に定義する代わりに、この1行で済む。(コード内では、$prop経由で各propにアクセスする)
// 薄いVueのラッパーを用意してやればこの行も不要になるはず
// (というか、ラッパー作るならコンストラクタでアレすればいいのでプラグイン自体不要な気がする)
$props: MyProps;
}
@prop
とかで頑張るよりも、こっちの方がいろいろとシンプルになるんじゃないかと思うんだけど、どうだろう、というようなことを考えている。
$emit
回りも似たような方法である程度型安全にできるんじゃないかという気がしていて、継続考慮中。
脚注
-
定番の vue-class-component に vue-property-decorator を組み合わせると
@prop
が使えるようになるし、僕が作ったvueit も同じような感じ。 ↩