Vue + TypeScript を模索して迷走した先にあるもの

これは 2019.01.29 スタートアップテック vol.2 の LT 資料です

@zaru

# Vue で TypeScript を使いたい

Vue は楽しいです。TypeScript も楽しいです。楽しい * 楽しいにしたいです。

## 現状確認および課題

- Vue だけであれば VueCLI で TypeScript サポート選べば OK
- Vuex も TypeScript で書ける
	- けどコンポーネントからの読み出しは恩恵を受けにくい
- Nuxt も v2.5 から TypeScript サポートが良くなる
	- 初期設定なしですぐ書けるようになる
- Vue template 内でのサジェストが弱い…

## 今回の目標

- とにかくサジェストされてほしい。いつでもどこでも

まずは VueCLI で TypeScript / Class スタイルな設定でプロジェクトを作る

```shell
$ vue create ts-proj
? Check the features needed for your project:
#=> Babel, TypeScript, Router, Vuex あたり

? Use class-style component syntax?
#=> Yes

? Use Babel alongside TypeScript for auto-detected polyfills?
#=> Yes
```

```vue
<template>
  <div class="home">
    <ul>
      <li v-for="todo in todos" :key="todo.id">{{todo.content}}</li>
    </ul>
  </div>
</template>

<script lang="ts">
import {Component, Provide, Vue} from 'vue-property-decorator'

interface Todo {
  id: number
  content: string
}

@Component({})
export default class Home extends Vue {
  @Provide() todos: Todo[]
}
</script>
```

普通の SFC で定義した配列

![1.gif](https://qiita-image-store.s3.amazonaws.com/0/2152/15f24f47-159b-97af-fbfb-16eec1d5a021.gif)

サジェストされてる！ （なぜか VScode はされなかった…）

次は vuex で試してみる。 `store.ts` に適当に state を生やしてみる。

```javascript
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

interface VuexTodo {
  id: number
  content: string
}

export interface State {
  vuex_todos: VuexTodo[]
}

export const state: State = {
  vuex_todos: []
}

export default new Vuex.Store({ state })
```

適当に `shims-vue.d.ts` とかで declare 拡張しておく

```javascript
import Vue from 'vue'
import * as Vuex from 'vuex'
import * as Store from './store'

declare module 'vue/types/vue' {
  interface Vue {
    $store: Vuex.Store<any>,
    $state: Store.State
  }
}
```

```vue
<template>
  <div class="home">
    <ul>
      <li v-for="todo in allTodo" :key="todo.id">{{todo.content}}</li>
    </ul>
  </div>
</template>

<script lang="ts">
  import {Component, Provide, Vue} from 'vue-property-decorator';

  @Component({})
  export default class Home extends Vue {
    get allTodo () {
      return this.$state.vuex_todos
    }
  }
</script>
```

`this.$state` 経由は拡張しているのでサジェストされる。でも template 内だとされない

![2.gif](https://qiita-image-store.s3.amazonaws.com/0/2152/4ede3c80-0459-a8da-1b05-e9acaeb81077.gif)

あと `this.$store` 経由の getters などはサジェストされない ( any にしてるから… )

state はサジェストされるが… getters `this.$store.getters['getAllTodo']` とかは厳しい。そこで次は `vuex-module-decorators` を入れてみる

`this.$store` 経由ではアクセスしないので `shims-vue.d.ts` を修正

```javascript
declare module '*.vue' {
  import Vue from 'vue'
  export default Vue
}
```

```javascript
import { Module, VuexModule, getModule } from 'vuex-module-decorators'
import store from "@/store"

interface ModuleTodo {
  id: number
  content: string
}
export interface ITodo {
  module_todos: ModuleTodo[]
}

@Module({ dynamic: true, store, name: "counter", namespaced: true })
class Todo extends VuexModule implements ITodo{
  module_todos: ModuleTodo[] = [] // state

  get getAllModuleTodo () { // getter
    return this.module_todos
  }
}

export const storeTodo = getModule(Todo)
```

Vuex store 初期化

```javascript
import Vue from 'vue'
import Vuex from 'vuex'

import { ITodo } from './store/todo'

Vue.use(Vuex)

export interface State {
  todo: ITodo
}
export default new Vuex.Store<State>({})
```

コンポーネント側で読み込んで使う。

```javascript

import {Component, Provide, Vue} from 'vue-property-decorator';
import {storeTodo} from '@/module/todo'

@Component({})
export default class Home extends Vue {
  get allTodo () {
    return storeTodo.getAllModuleTodo
  }
}
```

これで getters とか actions もサジェストされる！

![3.gif](https://qiita-image-store.s3.amazonaws.com/0/2152/420f3096-d2ef-2c27-31ac-623aa004a094.gif)

でも template ではまだサジェストされない

次は template でもサジェストがほしい。というわけで jsx (tsx) で書くようにしてみる。 `@vue/cli-plugin-babel` が必要

tsconfig.json で設定

```json
{
  "compilerOptions": {
    "jsx": "preserve",
    "jsxFactory": "h",
    // ...
  }
}
```

Home.tsx

```javascript

import { VNode, CreateElement } from 'vue';
import { Component, Vue } from 'vue-property-decorator';
import { storeTodo } from '@/module/todo'

@Component
export default class Todo extends Vue {
  public render(h: CreateElement): VNode {
    return <div>
      <ul>
        {storeTodo.getAllModuleTodo.map(todo =>
          <li>{todo.content}</li>
        )}
      </ul>
    </div>
  }
}
```

これで template でもサジェストされた！最高！
( JSX だから当然だけど )

![4.gif](https://qiita-image-store.s3.amazonaws.com/0/2152/23ce087f-684f-65a6-5643-2114e06e5467.gif)

## 注意

- JSX 採用すると Vue 独自の便利機能が使えない
	- `v-model` とかイベント系を自前で実装する必要がある
	- `vue/jsx` が beta だけど一応対応実装進めているので時間の問題？