TypeScript
vue.js
Vue.js #4Day 24

TypeScriptではじめるVueコンポーネント(vue-class-component)

こんな人向け

Vue.jsを使っているうちに以下のような考えになった人

  • TypeScriptでコンポーネントを書きたい
  • .vueファイルに抵抗がある(フォーマッタ、補完がそのままじゃ効かないし、色々設定するのは疲れる)
  • scoped cssはなくても大丈夫

前提知識

  • Vue.js、TypeScriptがなんとなくわかる

やること

  • コンポーネントをTypeScriptで書く。
  • (ついでに)テンプレート部分はhtmlファイルに分ける。

使うもの

  1. vue
  2. vue-class-component
  3. vue-property-decorator
  4. webpack

vue-class-componentってなに?

TypeScript(.tsファイル)でコンポーネントが書けるようになる。

Componentデコレータをつけて、Vueを継承したクラスとして書く。
例えば、空のコンポーネントはこう。

hello.ts
import Vue from 'vue'
import Component from 'vue-class-component'

@Component
class MyComponent extends Vue{

}

書き方

  • data
    クラスの変数として書く

  • 算出プロパティ
    getterとして書く

  • メソッド
    普通にメソッドとして書く

  • ライフサイクルフック
    名前を合わせてメソッドとして書く

詳しくはvue-class-componentを見れば大体わかる感じ。

vue-property-decoratorってなに?

vue-class-componentではpropsなどはComponentデコレータの中に書いていく。

hello.ts
import Vue from 'vue'
import Component from 'vue-class-component'

@Component({
  props: {
    hoge: String
  }
})
class MyComponent extends Vue {

}

vue-property-decoratorを使うと、デコレータを使って書ける
クラスメンバとして書けるので、this.って打つと補完にでる(※嬉しい)

hello.ts
import Vue from 'vue'
import Component from 'vue-class-component'
import { Prop } from 'vue-property-decorator';

@Component
class MyComponent extends Vue {

  @Prop({ type: String })
  hoge: string

}

$emitも、デコレータを使うとこんなふうに書ける。
クラスメソッドとして書けるので、メソッド名を文字列で書かなくていい(※嬉しい)

hello.ts
import Vue from 'vue'
import Component from 'vue-class-component'
import { Emit } from 'vue-property-decorator';

@Component
class MyComponent extends Vue {

  @Emit()
  change(newValue: string) { }

  selectItem(val: string) {
    this.change(val) // this.$emit('change', val) って書かなくていい
  }
}

他にも使えるデコレータが用意されているので
詳しくはvue-property-decoratorを見れば大体わかる感じ。

とりあえずコンポーネント作ってみよう

実際に作ってみる。
違いはないかもしれないけどVSCodeを使う前提。

環境構築

適当にフォルダ作って、必要なものを入れる

npm init
npm install -S vue
npm install -S vue-class-component
npm install -S vue-property-decorator
npm install -D typescript
npm install -D webpack
npm install -D ts-loader
npm install -D html-loader
npm install -D @types/node

package.jsonの設定

webpackでビルドするための行を追記する。

package.json
{
  // ...省略
  "scripts": {
    "build": "webpack"
  },
  // ...省略
}

tsconfig.jsonをつくって、追記する。

・importしたときに補完が効くようにする
・デコレータを使えるようにする

tsc --init

して、以下のとおり追記する。

tsconfig.json
{
  "compilerOptions": {
    // ...省略
    "baseUrl": ".", // pathsを設定するときはbaseUrlの設定が必要
    "paths": {
      "vue": [
        "/node_modules/vue/types/index"
      ],
      "vue-class-component": [
        "/node_modules/vue-class-component/lib/index"
      ],
      "vue-property-decorator": [
        "/node_modules/vue-property-decorator/lib/vue-property-decorator"
      ]
    },
    "experimentalDecorators": true, // デコレータを使うために必要
    // ...省略
  }
}

webpack.configをつくる

html-loaderの設定をしているのは、後ででてきますがテンプレート部分を別ファイルにするため。

webpack.config.js
module.exports = {
  entry: __dirname + "/main.ts",
  output: {
    path: __dirname + '/dist',
    filename: 'bundle.js'
  },
  module: {
    loaders: [
      { test: /\.ts$/, loader: 'ts-loader' },
      { test: /\.html$/, loader: 'html-loader?minimize=false' },
    ]
  },
  resolve: {
    extensions: ['.ts'],
    modules: [
      "node_modules"
    ],
    alias: {
      'vue$': 'vue/dist/vue.esm.js' // コンポーネントを使うときはこれを書く必要があるみたい
    }
  }
};

コンポーネントのファイルをつくる

クリックしたら”こんにちは”が"さようなら"に変わる。それだけ。

MyComponent.ts
import Vue from 'vue'
import Component from 'vue-class-component'

@Component({
  template: require('./MyComponent.html') // html-loaderを使うと外部のhtmlファイルを読み込める
})
export default class MyComponent extends Vue {
  // data
  hoge : string = 'こんにちは'

  // methods
  onClick(){
    this.hoge = 'さようなら'
  }

}
MyComponent.html
<div>
  <p @click="onClick">{{hoge}}</p>
</div>

コンポーネントの利用側をつくる

さっき作ったMyComponentを単に読み込んでいるだけ

main.ts
import Vue from 'vue'
import MyComponent from './MyComponent'

window.onload = () => {
  new Vue({
    el: '#app',
    components: {
      MyComponent
    }
  })
}
main.html
<html>

<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<script src="dist/bundle.js"></script>

<body>
  <div id="app">
    <my-component></my-component>
  </div>
</body>

</html>

ビルドして動作確認

npm run build

main.htmlを開いてちゃんと動いたらOK

おまけ

vue-class-componentを使う時に知っておくと嬉しいこと

継承

各コンポーネントに共通処理があるとき、継承を使ってまとめられる。
例えば、mountedのタイミングでコンソールに"mounted!!!"と表示する処理を共通化する場合は以下のように書く。

ComponentBase.ts
import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class ComponentBase extends Vue {

  mounted(){
    console.log('mouted!!!')
  }

}
MyComponent.ts
import Vue from 'vue'
import Component from 'vue-class-component'
import ComponentBase from './ComponentBase';

@Component({
  template: require('./MyComponent.html')
})
export default class MyComponent extends ComponentBase {
  // data
  hoge : string = 'こんにちは'

  // methods
  onClick(){
    this.hoge = 'さようなら'
  }

}

まとめ

TypeScriptでコンポーネントが作れた。ウレシス!