Vue3 チュートリアル 4 コンポーネントの活用

https://qiita.com/TakahiRoyte/private/9033b61a0e528324da4c

[【前回】 Vue3 チュートリアル 3 ToDoアプリの実装練習
](https://qiita.com/TakahiRoyte/private/a9aac2f069b99f3378c7)
前回作ったToDoアプリの一部をコンポーネント化して、機能を追加で実装していきましょう。

### ゴール

- コンポーネントの基礎を理解する
- コンポーネントを他のコンポーネントで利用できるようになる
- コンポーネント間の連携を理解する
    - `props`による値の受け渡し
    - イベントの`$emit`による連携
    - `slot`の利用

## ToDoアプリの機能追加

前回のToDoアプリを引き続き利用していきます。
今回追加で実装するのは以下の機能です。

- ToDoに詳細情報を入力できるメモデータを追加する
- 各ToDoをクリックすると詳細編集画面が表示されそこでメモを編集できる

説明の中で使用していくコードは以下の私の実装ベースで進めますが、
自分で作ったコードをそのまま使ってもらって読み替えながら進めてもらってもOKですし、
一旦リセットするために以下のコードを`App.vue`にコピーして使ってもらってもOKです。

```vue:App.vue
<script>
export default {
  data() {
    return {
      newTodoText: '',
      todos: [
        // { isDone: false, text: 'ToDoの文字列'}
      ],
    }
  },
  methods: {
    addTodo() {
      if (!this.newTodoText) return alert('文字を入力してください')
      this.todos.push({
        isDone: false,
        text: this.newTodoText,
      })
      this.newTodoText = ''
    },
    clearDoneTodos() {
      this.todos = this.todos.filter((todo) => !todo.isDone)
    },
  },
}
</script>

<template>
  <h1>My ToDo App</h1>
  <input type="text" v-model="newTodoText" /><button @click="addTodo">
    追加</button
  ><button @click="clearDoneTodos">完了済みを削除する</button>
  <p v-if="todos.length === 0">ToDoがまだありません！</p>
  <ul v-else>
    <li v-for="todo in todos">
      <input type="checkbox" v-model="todo.isDone" /><span
        :class="{ 'todo-done': todo.isDone }"
        >{{ todo.text }}</span
      >
    </li>
  </ul>
</template>

<style>
body {
  background-color: #eee;
}

.todo-done {
  text-decoration: line-through;
}
</style>
```

イメージ的には以下のような形でコンポーネントを作成していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89911/3629e1fb-1f60-5711-3591-df23c2c3adca.png)

実装開始時はすべてが`App.vue`に実装されているのでまずそれを`TodoAdd.vue`と`TodoList.vue`に切り出します。
続いて、ToDo編集モードを実装するために`TodoEdit.vue`を作成し、モードにより表示するコンポーネントを切り替えます。

こういったパーツはコンポーネント化することで、コンポーネント後の責務が明確になりメンテナンス性が上がる他、
再利用性が向上します（例えばTodoListだけ他のテンプレートで使い表示限定のページを作るなど）。

### 既存機能のコンポーネント化

まずはコンポーネントになれるために、既存の機能を2つコンポーネントに切り出してみましょう。
最初はToDoを追加するためのフォームを中心とした`TodoAdd.vue`を作ります。
`/src/components/TodoAdd.vue`のファイルを作成してください。
VSCodeはCtrl+2を押したり開いているファイルのタブをD&Dすることで以下の画像のように表示できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89911/1c597c8b-2970-1b57-eab7-d203e55090af.png)

`/src/components/TodoAdd.vue`のファイルを作成したら、一旦テンプレートの部分だけ`App.vue`から移行します。

```vue:TodoAdd.vue
<template>
  <input type="text" v-model="newTodoText" /><button @click="addTodo">
    追加</button
  ><button @click="clearDoneTodos">完了済みを削除する</button>
</template>
```

```vue:App.vue(テンプレート)
<template>
  <h1>My ToDo App</h1>
  <p v-if="todos.length === 0">ToDoがまだありません！</p>
  <ul v-else>
    <li v-for="todo in todos">
      <input type="checkbox" v-model="todo.isDone" /><span
        :class="{ 'todo-done': todo.isDone }"
        >{{ todo.text }}</span
      >
    </li>
  </ul>
</template>
```

この状態で保存をすると、突然`App.vue`からTodo追加用のフォームが消えてしまうので、画面表示も以下になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89911/11e39809-6a73-74fb-49f9-927aa3be467f.png)

これでは使えないので、`TodoAdd.vue`を`App.vue`でインポートしてフォームを表示してみましょう。
インポートするには`<script>`タグ内で指定のコンポーネントファイルをインポートして、
OptionsAPIの`components`オプションで登録する必要があります。

```vue:App.vue (script)
<script>
import TodoAdd from './components/TodoAdd.vue'

export default {
  components: {
    TodoAdd,
  },
  // 略
}
```

そして、インポートして登録したコンポーネントをテンプレートにて利用します。
`<h1>`タグのすぐ後の元の位置に`<TodoAdd />`を追記します。

```vue:App.vue (template)
<template>
  <h1>My ToDo App</h1>
  <TodoAdd />
  <p v-if="todos.length === 0">ToDoがまだありません！</p>
  <ul v-else>
    <li v-for="todo in todos">
      <input type="checkbox" v-model="todo.isDone" /><span
        :class="{ 'todo-done': todo.isDone }"
        >{{ todo.text }}</span
      >
    </li>
  </ul>
</template>
```

これで保存すると初期表示は問題なく動いているはずです！
ですが「追加」ボタンや「完了済みを削除する」ボタンが動作しません。
これはコンポーネントごとに`script`の内容のスコープは切られているため、
別コンポーネントに記述した`methods`や`data`などのオプションにはアクセスできないためです。
では、コンポーネント間の様々な連携はどうすれば良いのかと疑問が生まれる方もいるかもしれません。

その疑問を解消するのが、`event`と`props`と`slots`です
Vueの機能では他にもありますが、今回は基本となるこの2つを実装しながら連携を見ていきます。

### `event`の`$emit`による連携

`$emit`（発火）を使ってJavaScriptのイベントがコンポーネント間で伝播する連携を試します。
`$emit`は指定した名前のイベントを任意の引数とともに発火させることができます。
まずは`TodoAdd.vue`の完了済みを削除するボタンを押したら`delete-done`イベントが`$emit`させましょう。
そのためには`emits`オプションで発火するイベント名を定義し`$emit`で発火する処理を実装します。

```vue:AddTodo.vue
<script>
export default {
  emits: ['delete-done'],
  // 略
}
</script>

<template>
  <input type="text" v-model="newTodoText" /><button @click="addTodo">
    追加</button
  ><button @click="$emit('delete-done')">完了済みを削除する</button>
</template>
```

これで`delete-done`イベントが発火しています、といっても目に見える形ではありません。
こういったVueの内部的動作や処理を確認するために、自分でデバッグ用のコードを書いても良いのですが、
環境構築でインストールしてもらった[Vue.js devtools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd/related?hl=ja)を使うと良いでしょう。
未インストールの方は上のリンクからChromeもしくはEdgeにインストールしておいてください。

Devtoolsを使うには、Chromeの開発者ツールを開きます(F12)。
一番上のタブの右の方にVueがあるのでクリックするとVue.js Devtoolsが開きます。
表示されない場合は隠れてしまっている場合があるので`>>`マークを押して探してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89911/fcc77a04-66f8-4c90-cd2c-760f7f3ce16a.png)

初期表示は`Inspector`となっていて、コンポーネントの構造やコンポーネントの`data`の値を確認できます。
今回はイベントを確認したいので、`Timeline`を選択して、「完了済み削除」のボタンをクリックしてみてください。
するとタイムラインに紫と緑のドットが表示されます。
`Component Event`の欄をクリックすると、今実装した`delete-done`のイベントが発火されていることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89911/0e4a90eb-ce2f-4517-e4a8-252db4bc4f39.png)

この`delete-done`は前回説明した`v-on`で`click`や`keydown`などのイベントと同様に扱うことができます。
どういうことかというと、`App.vue`で`TodoAdd`をインポートして使っていますが、
そこに`@delete-done="xxx"`で`delete-done`イベントを拾ってJavaScriptの処理を実行できます。
そこで、以下のように`todos`にダミーの初期データを入れて`clearDoneTodos`を動かしてみましょう。

```vue:App.vue
<script>
import TodoAdd from './components/TodoAdd.vue'

export default {
  components: {
    TodoAdd,
  },
  data() {
    return {
      newTodoText: '',
      todos: [
        { isDone: false, text: 'ダミーTodo'}
      ],
    }
  },
  methods: {
    addTodo() {
      if (!this.newTodoText) return alert('文字を入力してください')
      this.todos.push({
        isDone: false,
        text: this.newTodoText,
      })
      this.newTodoText = ''
    },
    clearDoneTodos() {
      this.todos = this.todos.filter((todo) => !todo.isDone)
    },
  },
}
</script>

<template>
  <h1>My ToDo App</h1>
  <TodoAdd @delete-done="clearDoneTodos"/>
  （以下略）
</template>
```

この状態で、ダミーTodoにチェックをいれ、「完了済みを削除」ボタンを押してみてください。
ちゃんと消えていたら、`delete-done`のイベントを`App.vue`側で拾って処理を実施できているということになります。

このコンポーネントの`data`が正しく更新されているかもVue.js Devtoolsから確認できます。ついでに試してみましょう。
こんどは`Timeline`ではなく`Inspector`タブを選択します。
するとセンターにコンポーネントがどういう構造になっているのかが表示されます。
`todos`の配列データを定義しているのは`App.vue`なので`App`をクリックすると右側に`data`が表示されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89911/7addbe98-db08-8225-4244-294d6c681475.png)

画面をリロードしてダミーTodoを復活させ、また削除させてこの配列からデータが消えるか確認してみてください。

先程の`$emit`はテンプレートの中で実装しましたが、メソッドの中でも実行可能です。
`TodoAdd.vue`に新たな`addTodo`メソッドを作ってメソッド内で`this.$emit()`を発火します。
`emits`オプションへの`add-todo`の追加も忘れないようにしましょう。

```vue:TodoAdd.vue
<script>
export default {
  emits: ['delete-done', 'add-todo'],
  methods: {
    todoAdd() {
      this.$emit('add-todo')
    },
  },
}
</script>

<template>
  <input type="text" v-model="newTodoText" /><button @click="todoAdd">
    追加</button
  ><button @click="$emit('delete-done')">完了済みを削除する</button>
</template>
```

Vueのインスタンスに紐づく機能を使う時は`this.`を付ける必要があり`$emit`も同様です。
実装できたらまたVue.js Devtoolsからイベント発火されてるかを確認してみてください。
その上で`AddTodo`に`@add-todo="addTodo"`を追加しましょう。

```vue:App.vue
  <TodoAdd @delete-done="clearDoneTodos" @add-todo="addTodo"/>
```

このように複数イベントの`v-on`を登録することも可能です。
これで`App.vue`側の`addTodo`メソッドが動作しますが、期待どおりに動きません。
それもそのはず`newTodoText`に紐づいていた`input`要素は`TodoAdd.vue`コンポーネントに移動してしまい、
`v-model`による値の連携ができなくなっている状態にあります。
これを子コンポーネントのAddTodoからイベントの発火とともに値も連携する形に修正しましょう。

1. `TodoAdd.vue`の`data`に`newTodoText`を追加し、`App.vue`からは消す
1. `TodoAdd.vue`の`addTodo`の`$emit`の引数に連携する値として`newTodoText`を追加する
1. イベントを受け実行される`App.vue`側の`addTodo`にその値を引き渡し利用する
1. `newTodoText`の初期化の処理を`TodoAdd.vue`に移す

これを実装すると以下のようになっていきます。

```vue:TodoAdd.vue
<script>
export default {
  data() {
    return {
      newTodoText: '',
    }
  },
  methods: {
    todoAdd() {
      this.$emit('add-todo', this.newTodoText)
      this.newTodoText = ''
    },
  },
}
</script>

<template>
  <input type="text" v-model="newTodoText" /><button @click="todoAdd">
    追加</button
  ><button @click="$emit('delete-done')">完了済みを削除する</button>
</template>

```

```vue:App.vue
<script>
import TodoAdd from './components/TodoAdd.vue'

export default {
  components: {
    TodoAdd,
  },
  data() {
    return {
      todos: [{ isDone: false, text: 'ToDoの文字列' }],
    }
  },
  methods: {
    addTodo(newTodoText) {
      if (!newTodoText) return alert('文字を入力してください')
      this.todos.push({
        isDone: false,
        text: newTodoText,
      })
    },
    clearDoneTodos() {
      this.todos = this.todos.filter((todo) => !todo.isDone)
    },
  },
}
</script>

<template>
  <h1>My ToDo App</h1>
  <TodoAdd @delete-done="clearDoneTodos" @add-todo="addTodo" />
  （以下略）
```

`App.vue`では`addTodo`メソッドの引数に`newTodoText`が追加され、処理内で利用されています。
このように子コンポーネントから親コンポーネントへ値を連携し処理を実装していきます。

### `slot`の利用

`slot`は再利用性の高いコンポーネントを実装する上で重要な機能です。
`TodoAdd.vue`はコンポーネントとしての責務が明確なので、再利用性は低いですが、
たとえば`TodoAdd.vue`内で利用するボタンやフォームなどもコンポーネントとして実装して、
いろいろな場所で使い回すみたいなケースはよくあります。
そこで今回は`TodoAdd.vue`のボタンを`MyButton.vue`というコンポーネントに作り変えます。
`./src/components/basics/`ディレクトリを作成し`MyButton.vue`を作成してください。

```vue:MyButton.vue
<template>
  <button class="my-btn">
    <slot />
  </button>
</template>

<style>
.my-btn {
  color: #fff;
  background-color: #333;
  margin-left: 0.5em;
  border-radius: 0.5em;
}
</style>
```

`MyButton`コンポーネントがミニマルな作りです。
テンプレート内の第1階層の要素が`button`しかありません。
その中に`<slot />`が組み込まれていると思います。
これはこの`MyButton`コンポーネントを利用する際に、中身を親コンポーネント側で設定できるという仕組みです。
これが`TodoAdd.vue`でどう使われるかというと、以下のような実装になります。

```vue:TodoAdd.vue
<script>
import MyButton from './basics/MyButton.vue'

export default {
  components: {
    MyButton,
  },
  data() {
    return {
      newTodoText: '',
    }
  },
  methods: {
    todoAdd() {
      this.$emit('add-todo', this.newTodoText)
      this.newTodoText = ''
    },
  },
}
</script>

<template>
  <input type="text" v-model="newTodoText" /><MyButton @click="todoAdd">
    追加</MyButton
  ><MyButton @click="$emit('delete-done')">完了済みを削除する</MyButton>
</template>
```
テンプレート内の`<MyBUtton>`コンポーネントの中にボタンに表示したい文字列を書くことで、
それがボタンの文字列として正しく表示されていることと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89911/2561a4e2-0e54-f941-8b0f-f291c87c2cb1.png)

これは単純な文字列だけではなく、複数の要素を入れ込むことも可能です。
試しに画像ファイルを追加してみます。
以下のコードを`./src/assets/icon-plus.svg`として保存してください。

```svg:icon-plus.svg
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
<desc>Created with Fabric.js 1.7.22</desc>
<defs>
</defs>
<g transform="translate(128 128) scale(0.72 0.72)" style="">
	<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(-175.05 -175.05000000000004) scale(3.89 3.89)" >
	<path d="M 58.921 90 H 31.079 c -1.155 0 -2.092 -0.936 -2.092 -2.092 V 2.092 C 28.988 0.936 29.924 0 31.079 0 h 27.841 c 1.155 0 2.092 0.936 2.092 2.092 v 85.817 C 61.012 89.064 60.076 90 58.921 90 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
	<path d="M 90 31.079 v 27.841 c 0 1.155 -0.936 2.092 -2.092 2.092 H 2.092 C 0.936 61.012 0 60.076 0 58.921 V 31.079 c 0 -1.155 0.936 -2.092 2.092 -2.092 h 85.817 C 89.064 28.988 90 29.924 90 31.079 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
</g>
</g>
</svg>
```

そして以下のように追加ボタンに画像として追加してみましょう。

```TodoAdd.vue
<template>
  <input type="text" v-model="newTodoText" /><MyButton @click="todoAdd">
    追加<img src="../assets/icon-plus.svg" height="10" /></MyButton
  ><MyButton @click="$emit('delete-done')">完了済みを削除する</MyButton>
</template>
```

このように`slot`を使うことで汎用性の高いコンポーネントを作成可能になります。

ちなみにこの`MyButton`の実装を通してかなり鋭い方が気づくかもしれないポイントとして、
`MyButton`ボタンのクリックイベントを`TodoAdd`コンポーネント側で拾う必要があるのではという疑問が湧きます。
`MyButton`側で`click`イベントを発火しなければ`TodoAdd`の`@click`が動作しないのでは？ということです。
これは考え方としては正しいですが、Vueの補助的な機能として、「属性の継承」によりカバーされています。
どういうことかというと、コンポーネントのルート要素が1つの場合はイベントリスナーを特別設定せずとも、
継承されるということです。
もし`MyButton`コンポーネントのテンプレートに`<button>`以外のルート要素があったら、
`$emit`を使わないと期待する動作はしないということになります。
意味が伝わりづらいかも知れませんが、仮に以下のように`MyButton`のルートに2つの要素があった場合
（この場合は、`button`と`img`の2要素）、

```vue:MyButton.vue
<template>
  <button class="my-btn">
    <slot />
  </button>
  <img src="../../assets/icon-plus.svg" height="10" />
</template>
```

自分で`$emit`を使ってイベント発火を明示的にしなくてはいけません。
それは`TodoAdd.vue`でやっているのと同じことで、どのボタンでどのイベントがでるかを明示的にするということです。
ルート要素が1つであればいいので、上のコードに似ていますが、下のように書けば問題なくイベント継承がなされます。

```vue:MyButton.vue
<template>
  <div>
    <button class="my-btn">
      <slot />
    </button>
    <img src="../../assets/icon-plus.svg" height="10" />
  </div>
</template>
```

### `props`による値の受け渡し

Vueにはコンポーネント間の連携機能として`props`も存在します。
これは主に親コンポーネントから子コンポーネントに対して定められた値を渡すのに使います。
`slot`は内部の要素に限定されていますが`props`はより汎用的な値の受け渡しが可能です。
今回はTodoリストのリスト部分をコンポーネント化し、リストデータを`props`で渡しましょう。
まずはコンポーネントを外に切り出しダミーデータで表示できるか試してみます。
`./src/components/TodoList.vue`を作成し以下のように実装してみてください。

```vue:TodoList.vue
<script>
export default {
  data() {
    return {
      todos: [{ isDone: false, text: 'ToDoの文字列' }],
    }
  },
}
</script>

<template>
  <ul>
    <li v-for="todo in todos">
      <input type="checkbox" v-model="todo.isDone" /><span
        :class="{ 'todo-done': todo.isDone }"
        >{{ todo.text }}</span
      >
    </li>
  </ul>
</template>
```

これを`App.vue`でインポートしてついでにコードを修正します。
こちら側の`todos`も表示の確認の為に念のため残しておきましょう。

```vue:App.vue
<script>
import TodoAdd from './components/TodoAdd.vue'
import TodoList from './components/TodoList.vue'

export default {
  components: {
    TodoAdd,
    TodoList,
  },
  data() {
    return {
      todos: [{ isDone: false, text: 'ToDoの文字列' }],
    }
  },
  methods: {
    addTodo(newTodoText) {
      if (!newTodoText) return alert('文字を入力してください')
      this.todos.push({
        isDone: false,
        text: newTodoText,
      })
    },
    clearDoneTodos() {
      this.todos = this.todos.filter((todo) => !todo.isDone)
    },
  },
}
</script>

<template>
  <h1>My ToDo App</h1>
  <TodoAdd @delete-done="clearDoneTodos" @add-todo="addTodo" />
  <p v-if="todos.length === 0">ToDoがまだありません！</p>
  <TodoList v-else />
</template>
```

`v-on`の`@`が`TodoAdd`で使えたように`v-else`も`TodoList`につけられます。
カスタムコンポーネントにもディレクティブは使うことができると覚えておいてください。

この時点ではまだリストの表示には`TodoList`側の`data`を用いています。
これを`props`による受渡して親コンポーネントである`App.vue`から渡す形にします。

1. `TodoList`側で親コンポーネントから渡して欲しい`props`の`todos`を定義する
1. `TodoList`側の`data`の`todos`は削除してしまう
1. `App.vue`側から`<TodoList>`に`v-bind`を使って`todos`を渡す

```vue:TodoList.vue
<script>
export default {
  props: {
    todos: Array,
  },
}
</script>

<template>
  <ul>
    <li v-for="todo in todos">
      <input type="checkbox" v-model="todo.isDone" /><span
        :class="{ 'todo-done': todo.isDone }"
        >{{ todo.text }}</span
      >
    </li>
  </ul>
</template>
```

`props`で`Array`を`todos`で受け取るという定義をしています。
この`todos`は`data`で使っていたように`template`内で`v-for`でリスト表示可能です。
続いて`App.vue`側から`todos`を渡すようにしましょう。
テンプレート内で`v-bind`のコロン`:`を用いて`:todos=todos`と設定します。
名前は今回は一緒にしていますがこれは異なっていても構いません。

```vue:App.vue
<script>
// 略
export default {
// 略
  data() {
    return {
      todos: [{ isDone: false, text: 'ToDoの文字列' }],
    }
  },
//略
}

</script>

<template>
  <h1>My ToDo App</h1>
  <TodoAdd @delete-done="clearDoneTodos" @add-todo="addTodo" />
  <p v-if="todos.length === 0">ToDoがまだありません！</p>
  <TodoList v-else :todos="todos" />
</template>
```

この状態で最初の１行にダミーデータが表示されればよい状態です。試してみてください。
これで一通りの処理が実装できているはずです。
一度操作を色々確かめて見て、問題なさそうなら初期表示用のダミーデータもコメントアウトした状態で保存してください。

## まとめ

以上でコンポーネントの活用方法の基本を実装しながら学習しました。

- イベントの`$emit`による連携
- `slot`を使ったコンポーネントの実装
- `props`を使った値の連携

これらを活用することでコンポーネントを責務ごとに分割し、
わかりやすいコーディングを心がけることでメンテナンス性が上がります。
コンポーネントの設計思想はいろいろあって、今回は触れていませんが、
Atomic Designという考え方が一時期流行しました。

- Atoms: 最小単位のコンポーネント、単一の要素で構成されることが多い
- Molecules: Atomsを組み合わせた持つコンポーネント
- Organisms: AtomsとMoleculesを組み合わせた単一の責務を持つコンポーネント
- Templates: Organisms以下を組み合わせた複数の責務を持つコンポーネント
- Pages: ルーティングで切り替えるレベルのコンポーネント、データなどを持つ

Molecules/Organismsあたりの線引きが難しいので以下のように４階層に分けて設計するパターンもあります。

- basics
- components
- containers
- pages

何を選択すべきかはプロダクトの性質次第ですが、
チームの中で共通認識を持って取り組むことでコードの相互理解を進めていくことが重要です。

次回は編集モードをVueRouterの機能と共に追加していきます。

https://qiita.com/TakahiRoyte/items/231aa3ac3aacbe25ebeb