Vue.jsチュートリアル　〜Vue.js + TypeScriptでTrelloもどきを作ろう①〜

みなさん、お待たせしました。コスパエンジニアのyoshiharu2580です。
フロントエンド業務に就いてもうすぐ半年が経ちます。
まだまだ未熟者ですがこのあたりで少し初心に返るために、下のTrelloのようなタスク管理ツールを作成するチュートリアルを作りました。
![ezgif.com-video-to-gif (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308460/6bf101fc-d6fe-300d-524c-a52c1132bcb4.gif)
詳細な説明は公式ドキュメントに譲るとして、この記事ではとりあえず動かすことを目標とします。
Vue.js, TypeScriptが初めての方でも、なんとなく理解できましたら幸いです。

**対象読者**

* Vue.jsを触ったことがない人
* TypeScriptを触ったことがない人
* HTML, CSS, JavaScriptが少しわかる人

# 初期セットアップ
vue-cliで環境を構築します。([公式ドキュメント](https://cli.vuejs.org#getting-started))
**※2019.11.28時点でv 4.0.5**

```
$ yarn global add @vue/cli
$ vue create my-project
```

```
# 各項目の、:の後の内容を選択してください。

? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, TS, CSS Pre-processors, Lin
ter
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfi
lls, transpiling JSX)? Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported
by default): Sass/SCSS (with node-sass)
? Pick a linter / formatter config: Prettier
? Pick additional lint features: Lint on save, Lint and fix on commit
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedica
ted config files
```

プロジェクトが作成できたら起動してみましょう。

```
$ cd trello-clone
$ yarn serve
```

http://localhost:8080/

![スクリーンショット 2019-11-22 23.01.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308460/939848a2-39bc-b6fe-c33d-e57a9c54a720.png)

画面が表示されました。とても簡単ですね。

ここでVue.jsとTypeScriptの簡単な概要を説明します。

Vue.jsの基本は、**JavaScriptのデータとDOMを紐づけるだけ**のフレームワークです。
JavaScriptのリアクティブなデータが更新されると、それにあわせてDOMが自動的に更新されます。
まずは**見た目を変える**ために**データを変える**ということだけ覚えておきましょう。

TypeScriptは**型**があるJavaScriptです。
TypeScriptを導入することで、開発をわかりやすく安全にし、結果的に開発スピードを上げることができます。

さて、本題に戻ります。
初期セットアップとして`Hello.vue`を削除し、`App.vue`を以下のようにします。

```vue:App.vue
<template>
  <div id="app" />
</template>

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

@Component
export default class App extends Vue {}
</script>
```

まっさらになりましたね。それでは実際に作っていきましょう。

#型定義とリストレンダリング

ここではタスクを表示するものをカード、カードを束ねるものをリストと呼ぶことにします。
今回のアプリの要件は以下の通りです。

- リスト、カードの追加機能
- リスト名、カードのテキストの編集機能
- リスト、カードの削除機能
- リスト、カードの移動機能

機能を持つUIの部品のことを**コンポーネント**といいます。
Vue.jsなどのコンポーネントベースのライブラリでは、このコンポーネントを組み合わせてUIを作っていきます。

コンポーネントの設計では、まずデータの型の考察から始めます。
要件を満たすには、リスト、カードのデータ型はそれぞれ以下のようになります。

- リスト
  - 自身を特定するためにidを持つ
  - リスト名を持つ
  - カードの配列を持つ
- カード
  - 自身を特定するためにidを持つ
  - カードのテキストを持つ

`src`ディレクトリ配下に`types.ts`というファイルを作成し、TypeScriptで型を書いてみましょう。
オブジェクトの型定義には基本`interface`を使います。
コンポーネント名をそれぞれ`Card`, `List`とするので、ここではバッティングを防ぐためにオブジェクトの型にプレフィックスとして`interface`の頭文字Iを付けることにします。

```typescript:types.ts
export interface IList {
  /*
  ・idは途中で変えないので、readonly修飾子を付ける(値を変えるとエラーが発生)
  ・数値なのでnumber型
  */
  readonly id: number;
  name: string; // 文字列なのでstring型
  cards: ICard[]; // 配列を定義するには 要素[] とする
}

export interface ICard {
  readonly id: number;
  text: string;
}
```

モックデータを返すファクトリ関数を別ファイルに定義します。
関数に型をつける際はかっこの後に`: 型名`と書きます。
これで戻り値の構造が型と異なる場合にコンパイルエラーが発生します。安全ですね。

```typescript:initialData.ts
import { IList } from "@/types";

export function createInitialLists(): IList[] {
  return [
    {
      id: 1,
      name: "リスト1",
      cards: [
        {
          id: 1,
          text: "タスク1"
        },
        {
          id: 2,
          text: "タスク2"
        }
      ]
    },
    {
      id: 2,
      name: "リスト2",
      cards: [
        {
          id: 3,
          text: "タスク3"
        },
        {
          id: 4,
          text: "タスク4"
        }
      ]
    }
  ];
}
```

`App.vue`のような、拡張子が`.vue`のファイルをSFC（単一ファイルコンポーネント）といい、この中でコンポーネントを定義します。
`template`タグ内にHTML、`script`タグ内にJavaScript、`style`タグ内にcssを記述します。
`script`タグの属性に`lang="ts"`とすることでTypeScriptが使えるようになります。

Vueのリアクティブなデータの中で最も基本的なものが`data`です。
クラスのプロパティを定義することで`data`を登録することができます。
先ほど作成した`createInitialLists`を実行した返り値を`lists`に代入しましょう。
`data`には型を付けなくてもいいですが、付けることをおすすめします。

```vue:App.vue
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
  <script lang="ts">
  import { Component, Vue } from "vue-property-decorator";
+ import { IList } from "@/types";
+ import { createInitialLists } from "@/initialData.ts";

  @Component
  export default class App extends Vue {
+   lists: IList[] = createInitialLists();
  }
  </script>
```

これで`lists`という`data`にモックデータを設定することができました。

次に、カードのコンポーネント名を`Card`、リストのコンポーネント名を`List`として新しく作成しましょう。
こちらを`components`ディレクトリ配下に以下の内容であらかじめ作成しておきます。

```vue:List.vue
<template>
  <div />
</template>

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

@Component
export default class List extends Vue {}
</script>
```

```vue:Card.vue
<template>
  <div />
</template>

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

@Component
export default class Card extends Vue {}
</script>
```

作成した`List`コンポーネントを`App.vue`で使えるようにしましょう。
`@Component`デコレータの`components`オプションに`List`を登録すると、`template`内で`List`というカスタム要素として使用することができます。

リストレンダリング（配列の数だけDOM要素を描画）をするには`v-for`を使います。
リストレンダリングしたい要素の属性に`v-for="要素 in 配列"`を追加します。
こうすることで、配列の要素を属性, テキスト, 子要素のそれぞれで使うことができるようになります。
ここでは`v-for="list in lists"`としましょう。

`v-for`とセットで`key`属性も追加します。
`:key="一意な値"`とすることでレンダリングを最適化することができます。
ここでは`:key="list.id"`として、属性値にリストのidを与えましょう。

`:属性名（任意）="値"`をコンポーネントの属性に追加することで、渡された子コンポーネントではその属性名で値を受け取れるようになります。
属性値でJavaScriptの式を使うには`v-bind`ディレクティブ（省略記法は`:`）を使います。
`List`コンポーネントで`list`のデータを使うために、属性名をわかりやすく値の変数名と同じ`list`にして、値に`list`を代入しましょう。

```vue:App.vue
<template>
  <div id="app">
+   <List v-for="list in lists" :key="list.id" :list=list />
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
+ import List from "@/components/List.vue";
import { IList } from "@/types";
import { createInitialLists } from "@/initialData.ts";

+ @Component({
+   components: {
+    List
+  }
+ })
export default class App extends Vue {
  lists: IList[] = createInitialLists();
}
</script>
```

`List.vue`を見ていきましょう。
子コンポーネント内で親から渡されたデータ（これを`props`といいます）を受け取るには、`@Prop`デコレータを使って定義します。

`@Prop`デコレータの引数にはオプションのオブジェクトを渡すことができます。
そのオプションの`type`プロパティではざっくりとした型を付与することができます。
`list`はオブジェクトなので`Object`を指定しましょう。[他にはこんな値があります。](https://jp.vuejs.org/v2/guide/components-props.html#%E5%9E%8B%E3%81%AE%E6%A4%9C%E6%9F%BB)

`required`プロパティは、その`propsが必須かどうかを指定します。
`required: true`にしてpropsが渡されなかった場合はエラーが発生します。
できる限り`required: true`にしましょう。

App.vueで`list`として渡したので、受け取る際の変数名は`list`にします。
変数名の後には型を付与することができます。

```vue:List.vue
<template>
  <div>
+   {{ list.name }} <!-- JSの式を二重中括弧で囲うとテキスト展開される（マスタッシュ構文） -->
  </div>
</template>

<script lang="ts">
+ import { Component, Vue, Prop } from "vue-property-decorator"; // Propを追加
+ import { IList } from "@/types";

@Component
export default class List extends Vue {
+ @Prop({ type: Object, required: true })
+ list!: IList;
}
</script>
```
リスト名が表示されました。

`list`の後に`!`が付いていますね。
これはTypeScriptの**Non-null assertion operator**というものです。

まず**union型**について説明します。
union型とは複数の型の可能性があるということを表します。
型と型を`|`で繋ぐように書き、例えば、`string | number`は文字列型か数値型の可能性があるということです。

`props`は渡されない可能性があるので、`undefined`の可能性があります。
これを**オプショナルなプロパティ**といい、`undefined`とのunion型と推論されます。
オプショナルなプロパティが`undefined`ではないことを表現するためにNon-null assertion operatorを使います。

`@Prop`デコレータのオプションで`{ required: true }`としており、**親からデータが渡されないとエラーが発生することが担保されているので**、`!`を変数名の後ろに付けます。

同様にList.vue内でも`Card`をリストレンダリングしましょう。

```vue:List.vue
<template>
  <div>
    {{ list.name }}
+   <Card v-for="card in list.cards" :key="card.id" :card="card" />
  </div>
</template>

<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
+ import Card from "@/components/Card.vue";
import { IList } from "@/types";

+ @Component({
+   components: {
+     Card
+   }
+ })
export default class List extends Vue {
  @Prop({ type: Object, required: true })
  list!: IList;
}
</script>
```

あわせてCard.vueも変更します。

```vue:Card.vue
<template>
+ {{ card.text }}
</template>

<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
+ import { ICard } from "@/types";

@Component
export default class Card extends Vue {
+ @Prop({ type: Object, required: true })
+ card!: ICard;
}
</script>
```
リストレンダリングができました。
cssを使ってわかりやすくしましょう。（cssに関しては、各章の終わりの「この時点でのコミット」を参考にしてください。）
[この時点でのコミット](https://github.com/yoshiharu2580/trello-clone/commit/49fd14e1f6ada7e8444ef1e8f2767a27907a25c0)

#リストとカードの追加機能
次はリストの追加機能です。
フォームにリスト名を入力してEnterキーを押したら、新しくリストが追加されるようにしましょう。

ブラウザでは、ユーザーがDOM要素に対してクリックなどのアクションを起こした際などに**イベント**が発生します。
このイベントが発生した時に関数を呼び出すことができます。呼び出す側のことを**イベントリスナ**、呼び出される関数のことを特に**イベントハンドラ**といいます（とします）。
これらを実際にDOM要素に登録するには、`v-on`ディレクティブ（省略記法は`@`）を使って`@イベント名="イベントハンドラ"とします。
これで、登録したDOM要素でそのイベントが発生したときにイベントハンドラが呼び出されます。

なので、イベントハンドラ内で`data`を更新する処理を書けば、

1. ユーザーがアクションを起こすとイベントが発生し、イベントハンドラが呼び出される
2. イベントハンドラ内で`data`を更新する
3. dataが更新されるとVue.jsがDOMを更新する

という流れを作ることができます。

`data`を変更する処理は、基本`data`が存在するコンポーネント内で定義します。
`lists`があるのはApp.vueなのでApp.vueのクラス内のメソッドに定義します。

テキスト入力の際に発生するイベントには**inputイベント**と**changeイベント**があります。
inputイベントは入力する度に発生し、changeイベントはinput要素からフォーカスを外した際やEnterキーを押した際などに発生します。
ここでは`change`イベントリスナを選択します。
`change`イベントリスナに`addList`メソッドをイベントハンドラとして紐づけたものを、inputタグに登録しましょう。

```vue:App.vue
<template>
  <div id="app">
    <template v-for="list in lists">
      <div class="list-container" :key="list.id">
        <List :list="list" />
      </div>
    </template>
+   <input type="text" @change="addList" />
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import List from "@/components/List.vue";
import { IList } from "@/types";
import { createInitialLists } from "@/initialData.ts";

@Component({
  components: {
    List
  }
})
export default class App extends Vue {
  lists: IList[] = createInitialLists();
  // 値を返さない関数の返り値の型としてvoid型を付与
+ addList(): void {}
}
</script>
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
```

ここでリストの型についておさらいしましょう。リストの型は以下の通りです。

```typescript:types.ts
export interface IList {
  readonly id: number;
  name: string;
  cards: ICard[];
}
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
```

これにより、新しくリストを追加するには、

- 作成する度に一意のidが付けられ、
- フォームに入力した値がリスト名になり、
- 空のカードの配列を持った、

新しいリストを`lists`に追加すればいいことになります。

まずidです。idは、リストを作成した数+1とします。
この値は**更新される**ので、`listCreatedCount`としてdataに登録します。
初期値として`createInitialLists()`の戻り値の配列の要素数である２を代入します。（厳密には`createInitialLists().length`などにした方がいいかもしれません。）

次にリスト名についてです。
イベントハンドラをDOM要素に登録した際に`@change="addList"`と、`addList`に引数を与えていないので、メソッドの第一引数にイベントオブジェクト（発生したイベントの詳細な情報が詰まったオブジェクト）が渡されます。(`addList(event): void {}`)

このchangeイベントのイベントオブジェクトに型を付けましょう。
どのイベントにどの型をつければいいかは、TypeScriptの開発元であるMicrosoftの[こちらのサイト](https://microsoft.github.io/PowerBI-JavaScript/interfaces/_node_modules_typedoc_node_modules_typescript_lib_lib_dom_d_.globaleventhandlerseventmap.html)に載っています。（直接型定義を見ても構いません。`GlobalEventHandlersEventMap`型）
見てみると、
<img width="808" alt="スクリーンショット 2019-12-01 20.47.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308460/b1d83a26-e69b-a844-1576-f191bad2a058.png">
型名は`Event`ですね。この型はグローバルに登録されている（`tsconfig.json`というTypeScriptの設定ファイルで設定している）ので、`import`せずにそのまま使うことができます。
`event`に型`Event`を付与しましょう。(`addList(event: Event }): void {}`)

このイベントオブジェクトの中に、フォームに入力した値が入っています。
イベントオブジェクトの`currentTarget`プロパティが、イベントリスナが実際に登録されたDOM要素で、この中の`value`プロパティが目当てのそれです。
しかし、`Event`型には`currentTarget`プロパティがありません。
型`Event`は`load`イベントなど、DOM要素以外で発生するイベントの型でも使われているからです。

とりあえず、現状この`Event`型には`currentTarget`プロパティがないので、`currentTarget: HTMLInputElement;`（`HTMLInputElement`はinput要素の型）というプロパティを`Event`型に追加します。（新しく型を作ってもいいと思います。）
あるオブジェクトの型にプロパティを追加するには、そのプロパティを持つオブジェクト型を`&`を使って繋げます。
このようにしてできた型を**intersection型**といいます。
(`event: Event & { currentTarget: HTMLInputElement; }`)

それではここまでのコードを見てみましょう。

```vue:App.vue
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import List from "@/components/List.vue";
import { IList } from "@/types";
import { createInitialLists } from "@/initialData";

@Component({
  components: {
    List
  }
})
export default class App extends Vue {
  lists: IList[] = createInitialLists（）;
+ listCreatedCount = 2;

+ addList(event: Event & { currentTarget: HTMLInputElement }): void {
+   const newList = {
+     id: this.listCreatedCount + 1,
+     name: event.currentTarget.value,
+     cards: []
+   };
+   this.lists.push(newList);
    // listsに追加されたため、listCreatedCountをインクリメント
+   ++this.listCreatedCount;
    // フォームの値をリセットするために空文字を代入
+   event.currentTarget.value = "";
+ }
}
</script>
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
```

リストを追加できました。

同様にカードを追加するためのコードを書いていきましょう。
`List.vue`内にカードを追加するinput要素を追加し、input要素の`change`イベントリスナに`addCard`メソッドを登録しましょう。

```vue:List.vue
<template>
  <div class="list">
    {{ list.name }}
    <Card v-for="card in list.cards" :key="card.id" class="card" :card="card" />
+   <input type="text" @change="addCard" />
  </div>
</template>
```

`lists`は`App.vue`にあるので、カードを追加する処理は`App.vue`に書きます。

```vue:App.vue
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import List from "@/components/List.vue";
import { IList } from "@/types";
import { createInitialLists } from "@/initialData";

@Component({
  components: {
    List
  }
})
export default class App extends Vue {
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
+ addCard(): void {}
}
</script>
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
```

このままではList.vue内で`addCard`を定義していないため、エラーが発生してしまいます。
子コンポーネントで発生したイベントと親コンポーネントで定義したメソッドを紐付けるには、どのようにすればいいのでしょうか。
この場合には以下のようにします。

1. 親コンポーネント内で、子コンポーネントのカスタム要素の属性に、独自で定義した**カスタムイベント**のイベントリスナにイベントハンドラを登録する。
2. 子コンポーネント内のイベントハンドラで、そのカスタムイベントを発生（**emit**）させる。

以上のようにすることで、子コンポーネントで発生したイベントと親コンポーネントで定義したメソッドを紐付けることができます。

まず１から。
HTMLでは大文字は小文字に変換されてしまうため、カスタムイベントのイベントリスナ名は基本ケバブケースにします。
`App.vue`でメソッド名を`addCard`としたので、カスタムイベント名を`add-card`としましょうか。（`@add-card="addCard"`）
これで親コンポーネント側はOKです。

```vue:App.vue
<template>
  <div id="app">
    <template v-for="list in lists">
      <div class="list-container" :key="list.id">
+       <List :list="list" @add-card="addCard" />
      </div>
    </template>
    <input type="text" @change="addList" />
  </div>
</template>
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
```

次に２について。
メソッド名をカスタムイベント名のキャメルケースにし、`@Emit`デコレータを付与することで、そのカスタムイベントを`emit`することができます。
カスタムイベント名が`add-card`なのでメソッド名を`addCard`とします。

```vue:List.vue
<template>
  <div class="list">
    {{ list.name }}
    <Card v-for="card in list.cards" :key="card.id" class="card" :card="card" />
+   <input type="text" @change="addCard" />
  </div>
</template>

<script lang="ts">
+ import { Component, Vue, Prop, Emit } from "vue-property-decorator";
import Card from "@/components/Card.vue";
import { IList } from "@/types";

@Component({
  components: {
    Card
  }
})
export default class List extends Vue {
  @Prop({ type: Object, required: true })
  list!: IList;

+ @Emit()
+ addCard(event: Event & { currentTarget: HTMLInputElement }): void {}
}
</script>
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
```

ここで大事なことがあります。
子コンポーネントでemitするメソッドの**返り値**が、親コンポーネントで受け取れる**イベントオブジェクト**になるということです。
これでカードを追加するために必要なデータを送ることができます。

カードを追加するために必要なデータは、追加するリストのidとカードのテキストです。
これをまず`IAddCardEvent`として型定義しましょう。
型を定義して**子コンポーネント側のメソッドの返り値の型**、**親コンポーネント側メソッドの引数の型**として付与すれば、齟齬がなく型安全になります。

```vue:List.vue
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
<script lang="ts">
+ export interface IAddCardEvent {
+   listId: number;
+   text: string;
+ }

+ import { Component, Vue, Prop, Emit } from "vue-property-decorator";
import Card from "@/components/Card.vue";
import { IList } from "@/types";

@Component({
  components: {
    Card
  }
})
export default class List extends Vue {
  @Prop({ type: Object, required: true })
  list!: IList;

  @Emit()
+ addCard(event: Event & { currentTarget: HTMLInputElement }): IAddCardEvent {
    // 次の処理でリセットしてしまうので変数に格納
+   const text = event.currentTarget.value;
    // フォームの値をリセット
+   event.currentTarget.value = "";
    // 返す内容が複数あるのでオブジェクトで返す
+   return {
+     listId: this.list.id,
+     text
+   };
  }
}
</script>
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
```

この型を`App.vue`側のメソッドの第一引数に付与しましょう。
これで親コンポーネント側でも、このカスタムイベントのイベントオブジェクトの型が`IAddCardEvent`であることが約束されました。
（実際にはここまでする必要は無いかもしれません。将来、わざわざ型定義しなくても推論されるようになるといいですね。）

また、

```vue:App.vue
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import List from "@/components/List.vue";
import { IList } from "@/types";
import { createInitialLists } from "@/initialData";
+ import { IAddCardEvent } from "@/components/List.vue";

@Component({
  components: {
    List
  }
})
export default class App extends Vue {
  lists: IList[] = createInitialLists();
  listCreatedCount = 2;
+ cardCreatedCount = 4;
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
+ addCard({ listId, text }: IAddCardEvent): void {
+   const list = this.lists.find(list => list.id === listId);
    /*
    findは見つからなかった場合undefinedを返す可能性があるので、その場合は早期リターンする
    （ここではlist: IList | undefined）
    */
+   if (list === undefined) return;
+   const newCard = {
+     id: this.cardCreatedCount + 1,
+     text
+   };
    // ここではlist: IList
+   list.cards.push(newCard);
+   
+   ++this.cardCreatedCount;
+ }
}
</script>
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
```

これでカードの追加ができるようになりました。

ここでカスタムイベントのおさらいをしておきましょう。

1. ユーザーがイベントを発火
2. そのイベントに紐づけられた子コンポーネントのイベントハンドラが呼び出される
3. その子コンポーネントのイベントハンドラがカスタムイベントをemit（発火）する
4. そのカスタムイベントに紐づけられた親コンポーネントのイベントハンドラが呼び出される

という処理の順番になります。

[この時点でのコミット](https://github.com/yoshiharu2580/trello-clone/commit/fe18f01f2b0b4e2b15850b459bb3336c4b44a077)

[こちらに続く](https://qiita.com/yoshiharu2580/items/8f9c50da46d2c6e4e783)