Vue.js+TypeScriptでコンポーネントをv-modelでつなげてみる

## 概要

以下の記事でやり方がわかったので、TypeScriptでどう書くかまとめてみました。

vueでv-modelをネストして使いたい
https://qiita.com/xsota/items/010e7116c895b782b7a4


## 準備

Vue-Cliで環境を作ります。

GitHubに利用したプロジェクトをUPしています。実際に試してみたい方どうぞ^^
https://github.com/kai-kou/vue-js-typescript-nest-v-model


ここではDockerを利用して環境構築していますが、ローカルで構築してもらってもOKです。

```sh
> mkdir 任意のディレクトリ
> cd 任意のディレクトリ
> vi Dockerfile
> vi docker-compose.yml
```

```txt:Dockerfile
FROM node:10.8.0-stretch

RUN npm install --global @vue/cli

WORKDIR /projects
```

```yaml:docker-compose.yml
version: '3'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    volumes:
      - ".:/projects"
    tty: true
```

```sh
> docker-compose up -d
> docker-compose exec app bash
```

```sh:コンテナ内
> vue create app

Vue CLI v3.0.1
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, TS, Linter
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript for auto-detected polyfills? Yes
? Pick a linter / formatter config: TSLint
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No
? Pick the package manager to use when installing dependencies: (Use arrow keys)
❯ Use Yarn
  Use NPM
```

```sh:コンテナ内
> cd app
> yarn serve
```

これで実装の準備が整いました。


## 親コンポーネントに子コンポーネントを追加する

今回はVue-Cliでプロジェクト作成時に含まれているHelloWorldコンポーネントに子コンポーネントを足してみます。

```sh
> touch src/components/ChildComponent.vue
> vi src/components/ChildComponent.vue
```

```vue:src/components/ChildComponent.vue
<template>
  <input type="text" v-model="localValue"/>
</template>

<script lang="ts">
  import {
    Component,
    Emit,
    Prop,
    Vue,
  } from 'vue-property-decorator';
  
  @Component
  export default class ChildComponent extends Vue {
    @Prop() public value!: string;

    @Emit()
    public input(value: string) {}

    private get localValue(): string {
      return this.value;
    }

    private set localValue(value: string) {
      this.input(value);
    }
  }
</script>
```

親から受け取る```value``` を直接```v-model``` に指定できないので、get、setプロパティを間に挟んで、set時に```Emit``` してあげるわけですね。
へぇ。わかれば簡単。

注意点は```value``` プロパティと```input``` メソッド名を変更するとだめ。ってところでしょうか。

Vue.js：v-modelと$emitを使ってデータを読み書きする子コンポーネントをつくる
https://qiita.com/ozone/items/b75efe5c449cbc469b1e

>公式で以前読んだはずなのにすっかり忘れていたのですが、v-modelは実はただの糖衣構文。:value(prop)と@input(event)に展開して扱われます。

だそうです。


では、HelloWorldコンポーネントも変更します。
```msg``` プロパティを```v-model``` で取り扱えるように```value``` に変更します。
こちらも```value``` を直接```v-model``` に設定せず、get、setプロパティを間に挟みます。

```vue:src/components/HelloWorld.vue
<template>
  <div class="hello">
    <child-component v-model="localValue"/>
    <h1>{{ value }}</h1>
    (略)
  </div>
</template>

<script lang="ts">
import { Component, Emit, Prop, Vue } from 'vue-property-decorator';
import ChildComponent from './components/ChildComponent.vue';

@Component({
  components: { ChildComponent },
})
export default class HelloWorld extends Vue {
  @Prop() private value!: string;

  @Emit()
  public input(value: string) {}

  private get localValue(): string {
    return this.value;
  }

  private set localValue(value: string) {
    this.input(value);
  }
}
</script>
(略)

```

最後にApp.vueを変更します。HelloWorldコンポーネントの```msg``` プロパティがなくなったので、```v-model``` に変更します。

```vue:src/App.vue
<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <HelloWorld v-model="msg"/>
  </div>
</template>

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

@Component({
  components: {
    HelloWorld,
  },
})
export default class App extends Vue {
  private msg = 'Welcome to Your Vue.js + TypeScript App';
}
</script>
(略)
```

## 確認

ではブラウザで確認してみましょう。
テキストボックスの内容を変更すると、ちゃんと反映されると思います。
<img width="643" alt="スクリーンショット 2018-08-28 17.54.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/48549/9b2f0909-40f7-3126-a22e-790267d94fb0.png">


get、setプロパティを挟むのが手間ですが、まあまあ良いのではないでしょうか。



## 参考

vueでv-modelをネストして使いたい
https://qiita.com/xsota/items/010e7116c895b782b7a4

Vue.js：v-modelと$emitを使ってデータを読み書きする子コンポーネントをつくる
https://qiita.com/ozone/items/b75efe5c449cbc469b1e

Vue.js+TypeScriptで開発するときの参考記事まとめ
https://qiita.com/kai_kou/items/19b494a41023d84bacc7