[Vue.js] Vue.js 3.x + TypeScript + マテリアルデザイン ことはじめ その3

# これはなに？

[Vue.jsを個人的に始めてみた記録](https://qiita.com/sengoku/items/e630e236605bdbfd8de0)の続きです。

今度こそVuexについてやっていきます。

# おさらい : これから作るもの、今まで作ったもの

Hello Worldを作ります。

あえて入力フォーム画面と結果画面に分けてます。

```
+-入力フォーム画面---+               +-結果画面----------+
|                 |               |　　　　　　　　　　　|
| お名前: ______   | ==(Submit)==> | こんにちは{{名前}}  |
|                 |               |                  |
+-----------------+               +------------------+
```

## 今まで作ったもの

以下のようなファイルがあります。

* src/
    * views/
        * Home.vue : トップビュー（入力フォーム画面）コンポーネント
        * Result.vue : 結果ビュー（結果画面）コンポーネント
    * components/
        * HelloForm.vue : Hello World 入力フォームコンポーネント
        * HelloResult.vue : Hello World 結果表示コンポーネント
    * router/
        * index.ts : ルーティングの設定
    * store/
        * index.ts   : Vuexストア

# Hello Worldビジネスモデルを作成しました

とりま、ビジネス要件は分離しておきたいというところで、このような内容のファイル `src/business/hello-world.ts` を作成しました。
内部状態として**お名前**を保持します。
**メッセージ**を返します。

```ts
export class HelloWorld {
  private _name = "";

  // お名前
  get name(): string {
    return this._name;
  }

  // お名前
  set name(value: string) {
    this._name = value;
  }

  // Hello Worldメッセージ
  get message(): string {
    return `Hello ${this.name} !!`;
  }
}
```


# Hello Worldモジュールを作成しました

ここでは勉強のために、Hello World状態管理モジュール `src/store/modules/hello-world.ts` を別途作成しました。

```ts
import { HelloWorld } from "@/business/hello-world";

// state
const state = (): HelloWorld => new HelloWorld();

// getters
const getters = {
  message: (state: HelloWorld): string => state.message,
};

// actions
const actions = {};

// mutations
const mutations = {
  setName(state: HelloWorld, value: string) {
    state.name = value;
  },
};

export default {
  namespaced: true,
  state,
  getters,
  actions,
  mutations,
};
```

モジュールごとに名前空間が分かれるように `namespaced: true` を付けています。

`state`として、先に作成したクラスをそのまま使用することにしました。

`getters` には、メッセージを返しメソッドを追加しました。

`actions` は、特にやることがないので空です。

`mutations` には、名前をセットするメソッドを追加しました。



# Vuexストアを編集しました

先に作成したHello World状態管理モジュールを使用するようにしました。

Vuexストアを編集し、Hello World用のオブジェクトを追加しました。

```ts
import { createStore } from "vuex";
import helloWorld from "./modules/hello-world";

const store = createStore({
  modules: {
    helloWorld,
  },
});

export default store;
```

# Vuexストアを使用するようにしました

トップビューコンポーネントと結果ビューコンポーネントからVuexストアを使用するようにしました。

## トップビューコンポーネント

`computed`を使って、Vuexストアから値を返すようにしました。

```ts
computed: {
  name() {
    return this.$store.state.helloWorld.name;
  },
}
```

## 結果ビューコンポーネント

トップビューコンポーネントと同様に`computed`を使いました。

```ts
computed: {
  message() {
    return this.$store.getters["helloWorld/message"];
  },
}
```

# コンパイルエラーになりました

コンパイルエラーになりました。

```
TS2339: Property '$store' does not exist on type 'ComponentPublicInstance<{}, {}, {}, {}, {}, EmitsOptions, {}, {}, false, ComponentOptionsBase<{}, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, EmitsOptions, string, {}>>'.
```

`$store`の定義が存在しないためのようです。

[マニュアル](https://next.vuex.vuejs.org/guide/typescript-support.html#typing-store-property-in-vue-component) にちゃんとTypeScript使用時のお作法が書いてありました。

既存のファイルに倣って`src/shims-vuex.d.ts`というファイルを作りました。

```ts
import { ComponentCustomProperties } from "vue";
import { Store } from "vuex";

declare module "@vue/runtime-core" {
  interface ComponentCustomProperties {
    $store: Store<{}>;
  }
}
```




# コンポーネントを作り直しました

ビューコンポーネントから値を受け取り、イベントで値を返すように、Hello World入力フォームコンポーネントとHello World結果表示コンポーネントを作り直しました。

## Hello World入力フォームコンポーネント

次のようなコンポーネントにしました。

* プロパティ
    * `name` 入力フィールドのに表示する初期値を受け取る
* イベント
    * `submit` サブミットボタンが押された時発生する。イベント値として入力フィールドの値を伴う。

まずプロパティです。`defineComponennt`の中で次のようにしました。
`name`という名前のプロパティで値を受け取ります。

```ts
  props: { name: { type: String, default: "" } }
```

入力フィールドとバインドする値を定義しました。
名前は`name_`にしました。初期値としてプロパティに与えられた値を返しています。

```ts
  data() {
    return {
      name_: this.name,
    };
  }
```


テンプレートはこのようになりました。
`v-model`で`name_`にバインドしています。

```vue
<ui-textfield
 v-model="name_"
 :required="true"
 helper-text-id="name-field-helper"
 maxlength="240"
>
お名前
</ui-textfield>
```

次のようにイベントを定義しました。
名前は`submit`です。

```ts
  emits: ["submit"]
```


フォームのサブミットボタンが押されたときに、イベントを起こすようにしました。

```vue
<ui-button raised @click="$emit('submit', name_)">Submit</ui-button>
```



## Hello World結果表示コンポーネント

次のようなコンポーネントにしました。

* プロパティ
    * `message` 表示するメッセージを受け取る


```ts
  props: { message: { type: String } }
```

表示するテンプレートも作ります。

```vue
<div class="hello-result">{{ message }}</div>
```


# できあがったコンポーネント

出来上がったコンポーネントをまとめてみます。

## 入力フォームコンポーネント `HelloForm.vue`

### props

<dt>`name`
<dd>入力フィールドに表示する初期値を受け取る
</dd>

### data

<dt>`name_`
<dd>入力フィールドにバインドする
</dd>

### emits

<dt>`submit`
<dd>サブミットボタンが押された時に発生する。引数は入力フィールドの値
</dd>

## 結果表示コンポーネント `HelloResult.vue`

### props

<dt>`message`
<dd>表示するメッセージを受け取る
</dd>

## トップビューコンポーネント `Home.vue`

### computed

<dt>`name`
<dd>Vuexストアに格納されている値を返す
</dd>

### methods

<dt>`onSubmit`
<dd>入力フォームコンポーネントの`submit`イベントを受け取る
</dd>

## 結果ビューコンポーネント `Result.vue`

### computed

<dt>`message`
<dd>Vuexストアのgettersを呼び出して値を返す
</dd>

# 続きます

次でまとめようと思います。