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を継承したクラスとして書く。
例えば、空のコンポーネントはこう。


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

@Component
class MyComponent extends Vue{

}
```

### 書き方
- data
クラスの変数として書く

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

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

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

詳しくは[vue-class-component](https://github.com/vuejs/vue-class-component)を見れば大体わかる感じ。

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

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

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

}
```

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

```typescript: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も、デコレータを使うとこんなふうに書ける。
クラスメソッドとして書けるので、メソッド名を文字列で書かなくていい（※嬉しい）

```typescript: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](https://github.com/kaorun343/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でビルドするための行を追記する。

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

### tsconfig.jsonをつくって、追記する。
・importしたときに補完が効くようにする
・デコレータを使えるようにする

```
tsc --init
```
して、以下のとおり追記する。

```json: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の設定をしているのは、後ででてきますがテンプレート部分を別ファイルにするため。

```javascript: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' // コンポーネントを使うときはこれを書く必要があるみたい
    }
  }
};
```




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

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

```typescript: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 = 'さようなら'
  }
  
}
```

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

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

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

```typescript:main.ts
import Vue from 'vue'
import MyComponent from './MyComponent'

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

```html: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!!!"と表示する処理を共通化する場合は以下のように書く。

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

@Component
export default class ComponentBase extends Vue {
  
  mounted(){
    console.log('mouted!!!')
  }
  
}
```

```typescript: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でコンポーネントが作れた。ウレシス！