方針
Vue.js のテンプレート構文を使うと、テンプレートと JS 間の連携が型安全にできないので、 JSX (TSX) を使う。
TypeScript 2.1 までのやり方
Vue.js の forum にある、こちらの回答に詳しい。
この方法の不満は、回答の中には明記されていないが、最終的に vue-class-component を使った際、 props を繰り返し宣言する必要があり、その部分が型安全でないということだ。
// 上記の回答のリンク先 (https://github.com/ktsn/vue-tsx) のコードを引用, コメントを付与
import Component from 'vue-class-component'
import { Vue } from '../lib/index'
@Component({
props: {
name: String // #1
}
})
export default class App extends Vue<{ name: string /* #2 */}> {
name: string // #3
render (h: Function) {
return (
<div id="hello" title={this.name}>Hello {this.name}!</div>
)
}
}
番号を振った通り、都合 3 回 name: string
的な定義が必要で、かつどこかで typo (neme: string
とか) などしてもエラーにならない。
TypeScript 2.2 で可能になった型定義を利用する
ちゃんと説明しようとするとかなり長くなってしまうので端折るが、このように Vue
, Component
の定義をし直すと、以下のように書ける。
interface Props {
name: string
}
@Component<Props>({
props: {
name: String
}
})
export default class App extends Vue<Props> {
// name: string -> 不要
render (h: Function) {
return (
<div id="hello" title={this.name}>Hello {this.name}!</div>
)
}
}
冗長さは改善されていないものの、どこで typo があってもコンパイルエラーになるので安全だし、細かいところでは IDE の補完が効くようになるといったメリットもある。
なぜ、これが TypeScript 2.2 で可能になったか (TypeScript 2.1 でやろうとするとコンパイルが通らない) は、実は自分でもちゃんと分かっていないが、多分これのおかげ。こっちも関係あるかもしれない。
上記の型定義をし直した Vue
, Component
は、
$ npm i https://github.com/kimamula/vue-typed-for-tsx.git
すると、
import { Vue, Component } from 'vue-typed-for-tsx'
で使えるようになっている。
また、これを使った TodoMVC の実装も上げておいたので、どうぞご参考に。
npm にもそのうち publish するかもしれないが、型定義を変えただけなので、どこか公式のリポジトリに取り込んでもらうよう頑張ったほうがいいのかななどと思っている。
関連するリポジトリが vue, vue-class-component, babel-plugin-transform-vue-jsx とあるのが少し悩ましい。
余談: data について
Vue.js には props の他に data というのがあって、 React でいうと props が props, data がstate に相当する感じである。
で、この data をどう定義するかというと、これは vue-class-component
を使うと、インスタンス変数として初期値付で定義されたものが自動的に data になる。
@Component({
props: {
name: String
}
})
export default class App extends Vue {
name: string // props になる
id = 1 // data になる
render (h: Function) {
return (
<div id="hello" title={this.name}>Hello {this.name}! Your ID is {this.id}.</div>
)
}
}
実は本来の Vue.js の書き方により近いのは以下のような書き方で、実際にこれでも動くが、 props 同様冗長かつ型安全でなくなってしまうのを避けて、上の書き方を採用したものと思われる。
@Component({
props: {
name: String
},
data: function() {
return { id: 1 }
}
})
export default class App extends Vue {
name: string
id: number // Component の引数で定義されている場合は、こちらでは初期化不要
render (h: Function) {
return (
<div id="hello" title={this.name}>Hello {this.name}! Your ID is {this.id}.</div>
)
}
}
しかし、拙作の型定義を利用すれば、 data も props と同様インスタンス変数として宣言する必要がなくなり、 2 つ目のような書き方でも型安全にできる。
interface Props {
name: string
}
interface Data {
id: number
}
@Component<Props, Data>({
props: {
name: String
},
data: function() {
return { id: 1 }
}
})
export default class App extends Vue<Props, Data> {
render (h: Function) {
return (
<div id="hello" title={this.name}>Hello {this.name}! Your ID is {this.id}.</div>
)
}
}
書き方だけ見れば、 3 つ目より 1 つ目の方がシンプルだが、以下のような理由で、 vue-class-component で 1 つ目の書き方ができなくなってしまえばいいのに、と思っている。
- 1 つ目の書き方で定義されたインスタンス変数を data として扱うために、 vue-class-component は内部的に data を取得するためだけのインスタンス生成を行っている
- 「このインスタンス変数は data である必要がない」みたいなものがあっても、インスタンス変数は全て問答無用で data にされてしまう
3 つ目の書き方なら、 data は明示的に指定されるため、上記のような問題は起こらない。
そんなことを考えていたら、 vue-class-component を Vue.js 2.2.0 で使うと、 1 つ目の書き方でバグってしまう問題を見つけたので、これを契機にいい感じに持っていけないかと考え、とりあえず issue を立ててみた。