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



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

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ちょっと雑だけどパレットのUIできた。今回Vueの&lt;script setup&gt;とPiniaの組み合わせ試してみたけどほんと楽だわ。ゆるすぎて仕事で使うのは怖いけど、個人開発なら超アリ <a href="https://t.co/E7231yCpWq">pic.twitter.com/E7231yCpWq</a></p>&mdash; ゆき (@yuneco) <a href="https://twitter.com/yuneco/status/1465369078323769348?ref_src=twsrc%5Etfw">November 29, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

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

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

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

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

```typescript: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 }
}
```

```vuejs: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659469/99b160bb-b69c-1b09-997c-94d70b2e0e13.gif)

## 更に詳しく知りたい方はこちら
https://ics.media/entry/210929/

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

https://github.com/vuejs/pinia

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

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

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

```typescript: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
    }
})
```

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

```vue: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>
```

子コンポーネントです。

```vue: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659469/62d8914b-2ca8-d5c0-7ef6-233287a8f963.gif)

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