8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Vue3 チュートリアル 6 ダイナミックルーティングとPinia

Last updated at Posted at 2022-07-26

前回はVue Rouerを導入しましたが、色々と問題がでてきました。

  • ページ切り替えでインスタンス内のデータが消えてしまう
  • 編集モードに移行しても文字列が表示されないので編集できない

これらの問題を解決するために以下のゴールを達成していきましょう。

ゴール

  • Piniaによるグローバルストアでデータを管理する
  • Piniaのstate, actions, gettersのそれぞれを知り使う
  • Vue Rouerのダイナミックルーティングを知り、使う

Pinia

PiniaとはVueアプリケーション上でグローバルなストアを提供するライブラリです。
Vue2の時はVuexというストアのライブラリが利用されることが多かったですが、
その思想を受け継ぎながらもかなりシンプル化されて利用しやすさが上がったのがPiniaです。
Pinia自体はComposition APIでの利用を想定して作られましたが、OptionsAPIでも利用可能です。

Piniaのインストール

前回のVue Routerをインストールしたのと同じ要領でPiniaをインストールします。
今回は最新バージョンで良いので、その場合はバージョン指定は不要です。

$ npm install pinia

そうするとVue Routerの時と同じくpackage.jsonpiniaが追加されているはずです。
続いては、ソースコード側にもPiniaを使う設定をいれていきます。修正するのはまたもmain.jsです。
createPiniaをVueのapp.useで登録します。

main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'

const app = createApp(App)
app.use(router)
app.use(createPinia())
app.mount('#app')

Piniaのストアでstateを書く

環境ができたら、実際にストアを定義していきましょう。
srcディレクトリの中にstoresディレクトリを作成し、todos.jsファイルを作成します。
そしてpiniadefneStoreを使って、まずはstate(状態)としてtodosを定義しましょう。
todosはTodoオブジェクトの配列が入ります。

todos.js
import { defineStore } from 'pinia'

export const useTodosStore = defineStore('todos', {
  state: () => {
    return {
      todos: [
        {
          isDone: false,
          text: 'Todo in Pinia Store!',
        },
      ],
    }
  },
})

ここで定義したstateを読み込むようHome.vueを修正していきます。
順番に。まずはストア内のデータを表示できるか試してみましょう。
Home.vueのようにコンポーネントで利用する際は定義したストアを呼び出します。
先程作ったtodos.jsuseTodosStoreを呼び、piniaからmapStoresで利用します。

Home.vue
<script>
import TodoAdd from '@/components/components/TodoAdd.vue'
import TodoList from '@/components/components/TodoList.vue'
import { mapStores } from 'pinia'
import { useTodosStore } from '@/stores/todos'

export default {
  components: {
    TodoAdd,
    TodoList,
  },
  // ここは削除する
  // data() {
  //   return {
  //     todos: [
  //       //{ isDone: false, text: 'ToDoの文字列' }
  //     ],
  //   }
  // },
  computed: {
    ...mapStores(useTodosStore),
  },
  methods: {
    // 略
  },
}
</script>

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

// 略

dataに定義していたコンポーネントのデータは不要になったのでコメントアウト(削除)しています。
代わりにcomputedというプロパティが現れ、ここで...mapStores(useTodosStore)と実装しています。
この実装については順を追ってみていきます。

まずcomputedについてですが、これはcomputed(算出)プロパティと呼ばれます。
ストア専用のものではなく、元々はテンプレートの中に複雑なロジックを埋め込まないようにするためのものです。
例えばdataに持っている日付の文字列20220101をテンプレート内で表示するに当たり、
2022年01月01日と複数の場で表示したい要望があるとします。
それを{{ ${dateStr.substring(0,4)}年${dateStr.substring(4,6)}月${dateStr.substring(6,8)}日} }}
毎回テンプレート内に記述するとなると、可読性が低いコードが大量にテンプレート内に実装されしまいます。
これを避けるためにcomputedを使って、以下のように実装します。

computedの例
<script>
export default {
  data() {
    return { dateStr: '20220101' }
  },
  computed: {
    formattedDateStr() {
      return `${this.dateStr.substring(0,4)}${this.dateStr.substring(4,6)}${this.dateStr.substring(6,8)}日}`
    }
  }
}
</script>

<template>
  {{ formattedDateStr }} // 2022年01月01日
  {{ formattedDateStr }} // 2022年01月01日
  {{ formattedDateStr }} // 2022年01月01日
</template>

テンプレート内の可読性が悪いなと思ったらぜひ使ってみてください。

computedの値はもう1つ機能があって、それはcomputedのデータ参照元のデータが変更されると、
computedのデータも動的に算出されなおす、という動きがあります。
上の例のdataStrが変わるとcomputedformattedDateStrも変わるということです。
Piniaはこの機能を利用して、Piniaのストアのデータが変わると、
動的にコンポーネント内で利用する値も変わるわけです。この動きは後ほど確認します。
まずは、先程の実装を通して、todos.jsに定義したTodoアイテムがブラウザで表示されてるか確認してください。

image.png

この状態からEdit画面に一度いって、Home画面に戻ってきても同じ表示になっていると思います。
これはストアのTodosリスト情報を参照しているからです。ただ、現状ではリストを更新する手段が無いので実装していきます。

Piniaで値を更新するactionsを書く

Piniaの値を更新するメソッドはactionsと呼ばれます。
まずはtodosに新たなアイテムを追加するaddTodoactionを書いてみましょう。
コンポーネントに実装していたaddTodoとほとんど変わりはありません。

todos.js
import { defineStore } from 'pinia'

export const useTodosStore = defineStore('todos', {
  state: () => {
    return {
      todos: [
        {
          isDone: false,
          text: 'Todo in Pinia Store!',
        },
      ],
    }
  },
  actions: {
    addTodo(todo) {
      this.todos.push(todo)
    },
  },
})

これをコンポーネント側から呼び出して利用します。
mapStoresと同じように、今度はmapActionsをインポートして使います。
ここで一つ注意することがあります。
ストアに書いたaddTodoとコンポーネント側のmethodsaddTodoがあります。
これらのどちらも同じ名前のため、this.addTodoとしてアクセスしようとすると判別がつきません。
もとのaddTodoは新規でTodoを追加するためのメソッドなのでaddNewTodoと名前を変えましょう。

Home.vue
<script>
import TodoAdd from '@/components/components/TodoAdd.vue'
import TodoList from '@/components/components/TodoList.vue'
import { mapActions, mapStores } from 'pinia'
import { useTodosStore } from '@/stores/todos'

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

<template>
  <TodoAdd @delete-done="clearDoneTodos" @add-todo="addNewTodo" />
  <p v-if="todosStore.todos.length === 0">ToDoがまだありません!</p>
  <TodoList v-else :todos="todosStore.todos" />
</template>

テンプレート内で呼ぶメソッド名をaddNewTodoにするのもお忘れなく。
これで動作をするか確認してみましょう。

以前、Vue.js DevToolsを使ってコンポーネントの状態を確認したことがあったと思います。
同じようにPiniaもDevToolsによってストアの内容を確認できます。
実際に見てみましょう。

DevToolsのタブにPiniaというタブがあるのでクリックします。
するとストアごとに名前が表示されるのでそれを更にクリックすると、
今回はstatetodosだけなのでそれの状態が確認できますね。
試しに新たなTodoを追加してストアが更新されるか試してみてください。

image.png

追加はできるようですね。ただ、完了済みの削除は動かずコンソールにエラーが出てます。
そこで、次は同じ要領で、次は完了済みのTodoを消す処理の実装を試してみてください。
必要に応じて、以下のヒントを読んだり、その下にあるコードを参考にしてみましょう。
パターンAとBを用意したので、見る場合は順番に見ていってください。

ヒント:パターンA

  1. todos.jsactionsに削除する処理を書く
  2. Home.vueのmethodsから1.で定義した処理を呼び出す
  3. actionsmethodsのメソッド名がかぶってないかチェック(かぶってたら変える)
  4. mapActionsに1.で実装した処理を登録する
  5. 必要に応じてtemplate内で呼ばれている処理名も修正

おそらくエラーが出たらヒントのどこかでつまづいているはずです。
正解のコードの例を見てみましょう。

todos.js
export const useTodosStore = defineStore('todos', {
  // 略
  actions: {
    addTodo(todo) {
      this.todos.push(todo)
    },
    deleteDoneTodos() {
      this.todos = this.todos.filter((todo) => !todo.isDone)
    },
  },
})
Home.vue
<script>
// 略
  methods: {
    ...mapActions(useTodosStore, ['addTodo', 'deleteDoneTodos']),
    addNewTodo(newTodoText) {
      if (!newTodoText) return alert('文字を入力してください')
      this.addTodo({
        isDone: false,
        text: newTodoText,
      })
    },
    clearDoneTodos() {
      this.deleteDoneTodos()
    },
  },
// 略
</script>

これでも動作しますが、よりスマートな方法としてパターンBがあります。

ヒント:パターンB

  1. todos.jsactionsに削除する処理を書く
  2. Home.vueのmethodsから1.で定義した処理を呼び出す
  3. テンプレート内から直接呼び出した処理を実行するようにする

ステップ数が少ないですね。具体的なコードになるとどうなるかを見てみましょう。

todos.js
export const useTodosStore = defineStore('todos', {
  // 略
  actions: {
    addTodo(todo) {
      this.todos.push(todo)
    },
    clearDoneTodos() {
      this.todos = this.todos.filter((todo) => !todo.isDone)
    },
  },
})

さっきはactionsのメソッド名をわざわざ変えていましたが、今回はclearDoneTodosですね。
Home.vueの方も見てみましょう。

Home.vue
<script>
// 略
methods: {
    ...mapActions(useTodosStore, ['addTodo', 'clearDoneTodos']),
    addNewTodo(newTodoText) {
      if (!newTodoText) return alert('文字を入力してください')
      this.addTodo({
        isDone: false,
        text: newTodoText,
      })
    },
  },
//略
</script>

<template>
  <TodoAdd @delete-done="clearDoneTodos" @add-todo="addNewTodo" />
  <p v-if="todosStore.todos.length === 0">ToDoがまだありません!</p>
  <TodoList v-else :todos="todosStore.todos" />
</template>

違いにお気づきでしょうか。
今回は、入力チェックやその他の処理がないため、template内からmapActionsで登録された、
clearDoneTodosを直接呼び出して完了済みのTodoを削除するようにしています。
パターンAもわかりやすいといえばわかりやすいですが、少し冗長でした。

Piniaのgettersを使う

PiniaのストアにはVueコンポーネントのcomputedにも似たgettersを定義できます。
機能はあまり変わらず、stateの値をもとに動的に算出される値を提供します。
今回のTodosでは「完了済みのTodoの数を表示する」という用途で使ってみましょう。
全体のTodoの数はシンプルにlengthで取得できますが完了済みはisDonetrueのTodoのみに絞る必要があります。


8
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?