4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Componentに $props を生やすplugin(または型安全なVue Componentのはなし)

Posted at

ソース

こんだけ。

const VuePropsPlugin = {
    install(vue, options) {
        vue.mixin({
            beforeCreate: function() { this.$props = this; }
        });
    }
}

これは何?

vue.jsのコンポーネントに、 $props というインスタンスプロパティを生やすためのplugin

コンポーネントのprop(例えば foo)に this.foo だけでなく this.$props.foo という形でもアクセスできるようになる。

とりあえず、そういうプラグインがあった場合にどの程度うれしい試してみる目的のものなので、ウルトラ雑実装。($propsthis を突っ込んでいるだけなので、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 回りも似たような方法である程度型安全にできるんじゃないかという気がしていて、継続考慮中。

脚注

  1. 定番の vue-class-componentvue-property-decorator を組み合わせると @prop が使えるようになるし、僕が作ったvueit も同じような感じ。

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?