LoginSignup
6
9

More than 1 year has passed since last update.

<script setup>+CompositionAPI+Piniaでのグローバル状態管理

Last updated at Posted at 2021-12-15

script setup+CompositionAPI+Piniaでの状態管理があまりにも簡単で便利だったのですが、現時点で日本語での情報があまり無いようですので共有したいと思います(CompositionAPIを導入済みであればPiniaで簡単にグローバル状態管理ができますが、<script setup>を使うと記述量が減って更に簡単です)

CompositionAPIではReactのカスタムフックのようにローカルステートの保持と操作する為のロジックを別ファイルに切り出せる

カスタムフックスを知っている方は飛ばしてください

まず、アプリケーション全体から直接アクセスできる値をグローバルステート、Propsを使わないと他のコンポーネントには値を渡せない値をローカルステートとします。

簡単な例として、1秒ごとに画面上の数字が増加するコンポーネントのロジックをcounter.hooks.tsに切り出して実装してみます。

counter.hooks.ts
import {  onMounted, onUnmounted, ref } from "vue";

export const useCount = () => {

    const count = ref<number>(1)

    let clearID: number;

    onMounted(() => {
        clearID = window.setInterval(() => count.value++, 1000)
    })

    onUnmounted(() => {
        clearInterval(clearID)
    })


    return { count }
}
Counter.vue
<template>
  <h1 class="text-xl">{{count}}</h1>
</template>
<script lang="ts" setup>
import { useCount } from "@/counter.hooks";

const {count} =useCount()
</script>

結果

きちんと動いているようです。(GIFがちょっとおかしいですが、1秒毎に増加しています)
count.gif

更に詳しく知りたい方はこちら

Piniaを使ってグローバル状態管理をする

READMEに書いてある方法でも十分簡単なのですが、CompositionAPIを使ってstoreを定義する事で更に簡単にグローバル状態管理することができます。

例として、カートのような物を実装していきたいと思います。
親コンポーネントにカートの中身を表示するロジック、子コンポーネントにカートにアイテムを追加するロジックを実装するのですが、Propsで値の受け渡しを行わず、Piniaによるグローバルストアを用いて実装していきたいと思います。

まずはストアの定義から。

store.ts
export const useCartStore = defineStore('cart', () => {

    const cart = ref<Item[]>([])

    //getters
    const getCartItems = () => cart.value

    const getTotalFee = () => cart.value.reduce((sum, item) => sum + item.price, 0)

    //setters
    const addItemToCart = (item: Item) => cart.value.push(item)

    return {
        getCartItems,
        getTotalFee,
        addItemToCart
    }
})

親コンポーネントです。子コンポーネントとの値の受け渡しはしていません。

Parent.vue
<template>
  <h1>Parent</h1>
  <div class="flex" v-for="item in getCartItems()" :key="item.name">
    <p>{{item.name}}</p>
    <p>{{item.price}}</p>
  </div>
  <p>合計{{getTotalFee()}}</p>
  <Child></Child>
</template>
<script lang="ts" setup>
import { useCartStore } from "@/store/store";
import Child from "@/views/qiita/Child.vue"

const {getCartItems,getTotalFee} =useCartStore()
</script>

子コンポーネントです。

Child.vue
<template>
  <h1>Child</h1>
  <div>
    <div class="flex" v-for="item in itemList" :key="item.name">
      <p>{{ item.name }}</p>
      <p>{{ item.price }}</p>
      <button class="border-black" @click="addItemToCart(item)">追加</button>
    </div>
  </div>
</template>
<script lang="ts" setup>
import { useCartStore } from "@/store/store";
import { Item } from "@/views/qiita/item.interface";
import { ref } from "vue";

const {addItemToCart} = useCartStore();

const itemList = ref<Item[]>([
  {
    name: "りんご",
    price: 100
  },
  {
    name: "バナナ",
    price: 50
  }
])
</script>

結果

期待通りに動いています。

parent.gif

まとめ

CompositionAPIの登場によってVueがReactに近くなり、「Reactでいいのでは?」という声も聞こえてきそうですが、ここまで簡単にグローバル状態管理ができるのはVueの強みだと思います。

6
9
0

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
6
9