簡単なForm作成から学ぶVue(TypeScript)の書き方 その1

# vueコンポーネントを作成する。

〜contents〜

* PropとEmit
* v-bindとv-on

## コンポーネントとは？

Vueでは、各機能の最小単位の部品をコンポーネントとして創り、必要な各箇所でその部品を組み合わせてページを作っていく。
今回のFormの例では、子コンポーネントはテキスト入力の部品コンポーネントやボタンの部品コンポーネントが相当し、これらを束ねるFormコンポーネントが親コンポーネントという関係になる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/e7977f5b-d2a2-4809-c35d-3de5dcdb21a0.png)

vueファイルのざっくりした理解。

```vue
<template>
  <!--HTMLを書く空間-->
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'; //おまじない

@Component //おまじない
export default class コンポーネント名 extends Vue {
  /////////////コンポーネント内で使用する変数、関数の定義//////////////
}
</script>

<style scoped lang="scss">
  /////////////スタイルを書くところ//////////////
</style>
```

`vue-property-decorator`は、TypeScriptで書く用のツールらしい。

各コンポーネントは自身の変数の値や関数コールを知ることはできるが、このままでは外部の値を知ることはできない。
つまり、テキスト入力のコンポーネントに何を入力されても、親のFormコンポーネントからは知れないし、ボタンのコンポーネントは入力値に基づいた活性非活性を分岐することはできない。

そこで、各コンポーネント間で値の授受や、関数コールを行う仕組みを利用する。それが`Prop`と`Emit`である。

重要なことはこれらは一方向の関係であること。
`Prop`は親コンポーネントから子コンポーネントへ値を渡す時に使用し、`Emit`はこコンポーネントから親コンポーネントの関数をコールする時に使用する。（逆に、子は親の変数を直接変えないし、親が子の関数は動かさない。）

__まとめ__

> Prop：親→子へのデータ受け渡し
> Emit：子→親へのデータ受け渡し

## つくってみる

ここでは、TextBoxに入力があったらボタンを活性にするFormを作る。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/44bca31b-30e5-12b2-369f-5a10f5d450a1.png)

流れは、TextBoxコンポーネントで入力されたことをトリガにFormコンポーネントへEmitして値が入力されたことを通知する。次に、FormコンポーネントではPropを用いてButtonコンポーネントの活性を変更する。

まずは、`Prop`と`v-bind`の扱い方。

### Form(親)からButton(子)へ活性非活性を指定する。

```vue:親コンポーネント
<template>
  <div>
    <Button v-bind:disabled="disabled"/>
  </div>
</template>

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

@Component({
  components:{
    Button
  }
})
export default class Form extends Vue {
  private disabled: boolean = false
}
</script>
```

```vue:子コンポーネント
<template>
  <div>
    <button class="button" :class="{disabled: disabled}">決定</button>
  </div>
</template>

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

@Component
export default class Button extends Vue {
  @Prop()
  private disabled: boolean
}
</script>

<style scoped lang="scss">
.button{
  text-align : center;
  background-color : #f0b434; //活性の色
  cursor : pointer;
  border : solid 1px;
  float : center;
}
.disabled{
  background-color : #7d837d; //非活性の色
}
</style>
```

子コンポーネントでの`@Prop`は親コンポーネントから受け取るデータとして宣言している。
これにより変数`disable`は親コンポーネントから代入できる。

```
  @Prop()
  private disabled: boolean //子コンポーネントの変数。
```


では親からはどのように代入するか？
まずは、Buttonコンポーネントが使えるようにならないといけないのでインポートする。

```js:親コンポーネント(Form)
//インポート
import Button from '@/components/Button.vue'

//コンポーネントの定義
@Component({
  components:{
    Button
  }
})
export default class Form extends Vue {
  private disabled: boolean = false //親コンポーンネントの変数。
}
```

親コンポーネントからButtonコンポーネントの`disable`に値を渡してあげる。この時、渡す対象の子コンポーネントの値には`v-bind:`を付して、呼び出す際に値を指定する。

```html:親コンポーネント(Form)
    <Button v-bind:disabled="disabled"/>
```

上記のケースでは、ボタンが使用可能かを示す`disabled`は`v-bind:`が付され、代入される「disable」は定数ではなく、親コンポーネントの`disabled`という変数である。なお、`v-bind:`は省略して`:`と表記することもできる。

親から指定されると`disable`により子コンポーネントの`class`に`disabled: false`指定され、`disable`スタイルが外れる。

```
<button class="button" :class="{disabled: disabled}">決定</button>
```

なお、`<button class="button" :class="{disabled}">決定</button>`としても同じ。

__まとめ__

> 変数を渡す方法
> <コンポーネント名 v-bind:子コンポーネントの変数="親コンポーネントの変数" />

> 変数を受け取る方法
> @Prop()
> //子コンポーネントの変数定義

次に`Emit`と`v-on`について

### Button(子)からForm(親)へクリックされたことを通知する。

```vue:親コンポーネント
<template>
  <div>
    <Button v-bind:disabled="disabled" v-on:Clicked="click"/>
  </div>
</template>

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

@Component({
  components:{
    Button
  }
})
export default class Form extends Vue {
  private disabled: boolean = false
  private click(): void{
    console.log("I'm parent")
  }
}
</script>
```

```vue:子コンポーネント(スタイル省略)
<template>
  <div>
    <button class="button" :class="{disabled: disabled}" v-on:click="Clicked">決定</button>
  </div>
</template>

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

@Component
export default class Button extends Vue {
  @Prop()
  private disabled: boolean

  //Emitの引数のイベント名はメソッド名と同じ場合は省略可能。この場合省可能
  @Emit('Clicked')
  private Clicked():void {
    console.log("I'm child")
  }
}
</script>
```

`@Emit`は子コンポーネントから親コンポーネントへ連絡する際に用いる。書式は以下。

```:構文
@Emit('親側で関数を指定する時のイベント名')
private 子の中で呼ぶ時のメソッド名():　戻り値{
  //処理
}
```

トリガされる方では以下。

```:構文
v-on:イベント名="呼び出す親自身の関数"
```

上記の例では、子コンポーネントの`v-on:click="Clicked"`によりクリックイベントが発生した際に子コンポーネント自身の関数`Clicked`が呼び出される。さらに、この関数`Clicked`はEmitが指定されており、この関数`Clicked`が呼ばれると、引数に指定した親コンポーネントの`Clicked`イベントを発生させる。
親コンポーネント側では`v-on:Clicked="click"`により`Clicked`イベント発生をトリガにして親自身の関数`click`が呼ばれる。

このようにして、このボタンコンポーネントをクリックすると以下のようにコンソールに出力される。

```
I'm child
I'm parent
```

なお、上の例のようにイベント名＝メソッド名の場合にはEmitの引数を省略することができる。
また、大文字小文字は区別されず、キャメルケースとケバブケースは勝手に変換され、バインドできる。

`v-bind`同様に略記法があり、`v-on:`は省略して`@`と表記することもできる。

# Appendix

```
Property '~' has no initializer and is not definitely assigned in the constructor
```

実行時に上のエラーが鬱陶しい場合、tsconfig.jsonに以下を追記すると解消する。

```
"strictPropertyInitialization": false
```

# お世話になった参考サイト

https://orizuru.io/blog/vue-js/vue_emit-props/