はじめに
この記事を書いて1年経ちました。
1年間でVue.jsとTypeScriptの環境は大きく変わりました。まずは、その辺を色々と紹介していきます。そのあと、サンプルを作成します。
2017年3月19日に追記
作成したサンプルがエラー吐くとのissueを受けたのを機に、サンプルを結構いじりました。サンプルも必ずチェックしてね。
この1年間で変わったこと
Vue.js 2.0がリリースされた
新しいメジャーリリースに際し、TypeScript関連で一番大きく変わったのは、型定義ファイルの提供方法です。バージョン1まではDefinitelyTypedに型定義ファイルを登録していました。そのため、tsdやtypingsで型定義ファイルをダウンロードする必要がありました。
一方新しいバージョンからは、Vue.jsのリポジトリ内に型定義ファイルがあります。したがって、npmなどを使ってライブラリをダウンロードするだけで使えるようになります。Vue.js本体だけでなく、いくつかの公式プラグインにも提供されています。
現在のところ、型定義ファイルがバンドルされているパッケージは以下の通りです。
- vue
- vuex
- vue-router
- vue-class-component (ソースコードがTypeScriptで記述されています)
TypeScript 2.0がリリースされた
TS 2.0で色々と大きな変更が入りました。詳しくはこちらを参考にしてください(TypeScript 2.0 Beta 変更点 - Qiita)。Vue.js向け型定義ファイルを作成する上で、嬉しかった変更は、function
内で this
を明示できるようになったことです。
Vue.jsでコンポーネントを定義するとき、 ComponentOptions
の中にプロパティやメソッド、ライフサイクルフックを記述します。
export default {
data () {
return {
key: ''
}
},
mounted () {
this.$watch(this.key, 'methodName') // 型が合っているかわからない
},
methods: {
methodName () {
// something
}
}
}
TS 1.xではこのmounted内部の this
を明示できず、this
は any
型になってしまいました。
TS 2.0からは、以下のように記述でき、function
内部の this
の型を明示できるようになりました。例えばこんな感じです。
import * as Vue from 'vue'
export interface MyComponent extends Vue {
key: string
}
export default {
mounted (this: MyComponent) {
this.$watch(this.key, 'methodName')
}
}
型定義ファイルには、interface ComponentOptions<V extends Vue>
が定義されています。これを利用することで、ComponentOptions
内部のすべての this
を自動的に明示できるようになります。以下のような感じで使います。
import { ComponentOptions } from 'vue'
export interface MyComponent extends Vue {
key: string
}
export default {
mounted () {
this.$watch(this.key, 'methodName')
},
beforeDestroy() {
this.$off('key')
}
} as ComponentOptions<MyComponent>
また、後述するように、JavaScriptだけではなくHTMLやCSSなどに対しても型定義ファイルを作れるようになったことも嬉しいです。
作るもの
昨年と同様、サンプルアプリケーションを作ろうと思います。
明日12月4日は、『ご注文はうさぎですか?』に登場するキャラクター「香風智乃」の誕生日であります。したがって彼女の誕生日をお祝いするアプリケーションを作ろうと思います。
昨年との変更点は、Vuexを導入したことと、jQueryを使わなくなったことと、データの永続化にDexie.jsを使ったことです。
方法
- TypeScriptで記述する
- テンプレートはHTMLファイルに記述する
- CSSはSCSSで記述する
- Webpackを使う
デコレータについて
vue-class-component
以外にも色々あります。
2.0に対応しているものをリストアップします。
- https://github.com/HerringtonDarkholme/av-ts
- https://github.com/locoslab/vue-typescript-component
- https://github.com/wonderful-panda/vueit
- https://github.com/InDIOS/vue-ts-decorate
今回はVue.js公式のvue-class-component
を使います。
また、vue-property-decoratorも合わせて利用します。
TypeScriptのヘルパー関数について
デコレータを使うと、ヘルパー関数が何度も生成されてファイルサイズが増えてしまいます。
TypeScript2.1からは、tslib を使うことで防ぐことができます。
TypeScript 2.1.1 変更点 - Qiitaによると、
-
tslib
をnpmなどでダウンロードする。 -
tsconfig.json
内のimportHelpers
とnoEmitDecorators
をtrue
にする。
のようにします。
Viewについて
2.0より前のバージョンでは、template
プロパティにHTMLを記述したりテンプレートのidを記述したりしていました。
一方2.0からは仮想DOMが採用されました。したがって仮想DOMを構築する関数の中身を用意することが必要となりました。これは日本語ドキュメントにおいて描画関数と訳されています。
Vue.jsは現在2.1が最新バージョンですが、https://github.com/vuejs/vue/tree/dev/dist をみるといっぱいビルドファイルが並んでいます。注目していただきたいのは、compiler-included
と runtime-only
の違いです。前者はテンプレートから描画関数を生成するコンパイラが含まれていますが、後者には含まれていません。
したがって、後者を利用するときはいかにして render
及び staticRenderFn
といった描画関数を用意するかがポイントになります。
自分で描画関数を書く
デコレータ内部に記述するか、クラス定義の中に記述します。
型チェックが効くのが嬉しいです。
import * as Vue from 'vue'
import Component from 'vue-class-component'
@Component<Vue>({
render (h) {
return h('div', this.message)
}
})
class MyComponent extends Vue {
message = 'Hello world'
render (h: Vue.CreateElement) {
return h('div', this.message)
}
}
WebpackのloaderでHTMLをコンパイルする
HTMLファイルからVue.jsの描画関数を生成するWebpackのloaderは2つあります。
- https://www.npmjs.com/package/vue-template-loader
- https://www.npmjs.com/package/vue-template-compiler-loader
この2つの違いは、render
と staticRenderFn
をどのように提供しているかということです。
ここで、型定義ファイルを書いてみることにします。
declare module '*.html' {
import * as Vue from 'vue'
import { ComponentOptions } from 'vue'
var template: <V extends Vue>(options: ComponentOptions<V>) => ComponentOptions<V>
export = template
}
import * as Vue from 'vue'
import Component from 'vue-class-component'
import * as template from 'my-template.html'
// thisを明示するときは、 @Component(template<MyComponent>({}))と書きます。
@Component(template({}))
class MyComponent extends Vue {
}
declare module '*.html' {
import * as Vue from 'vue'
import { CreateElement, VNode } from 'vue'
export var render: (this: Vue, createElement: CreateElement) => VNode
export var staticRenderFns: ((createElement: CreateElement) => VNode)[]
}
import * as Vue from 'vue'
import Component from 'vue-class-component'
import { render, staticRenderFns } from 'my-template.html'
@Component({ render, staticRenderFns })
class MyComponent extends Vue {
}
// もしくは
import * as template from 'my-template.html'
@Component({
mixins: [template]
})
class MyComponent extends Vue {
}
今回は前者を使いました。
HTMLWebpackPluginと併用すると、エラーを吐いてしまいました。したがってコンポーネントのテンプレートは拡張子を*.template.html
としました。
シングルファイルコンポーネントを使う
ts-loader
と組み合わせることで、*.vue
内でTypeScriptを使えるようです。
作ったもの
ここにあります。
ダウンロードして、dist/index.html
をブラウザで開いてみてください。
考察
よくできたと思います。
最後に
最近のVue.jsについて
以前は数あるその他フロントエンドフレームワーク的な存在だったVue.jsは、今やGitHubのスター数でこんな感じになっています。0.10の頃から注目していたので、知名度が大きくなったことはとても嬉しく思っています。
コードネームについて
2.0は攻殻機動隊 (Ghost in the Shell) でした。
次はHUNTER×HUNTERとかどうでしょう。
また1.1はFになるようですが、蒼穹のファフナーか鋼の錬金術師になると信じています。
明日12月4日は@nasumさんです。