Vue の状態管理ライブラリ Pinia を試してみる

`Vue` で使える状態管理のライブラリ `Pinia` を試してみたのでメモです。
アイコンかわいい ！　ドキュメントは [コチラ](https://pinia.esm.dev/)

### 注意
この記事を書いている時点(2021/04/04) ではまだ`alpha`版です！！

## インストール

```
npm install pinia@next
```

Vue2 でも使えるみたいですがその場合は
`pinia@latest` と `@vue/composition-api` をインストールすれば使えるようです。(試していません。)

## app に Pinia を追加する

```ts:main.ts
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import { createPinia } from "pinia";

createApp(App)
  .use(router)
  .use(createPinia()) //追加
  .mount("#app");
```

## Store の定義

```ts:useUserStore.ts
import { defineStore } from "pinia";

export const useUserStore = defineStore({
  //idでストアを特定している。
  id: "user",

  //stateの定義、初期値を返す関数を定義する。
  state: () => ({
    firstName: "",
    familyName: "",
  }),

  getters: {
    fullName() {
      //thisでstateを参照できる。
      //補完も有効
      return `${this.firstName}・${this.familyName}`;
    },

    hello() {
      //ゲッターも参照できる。
      return `Hello! ${this.fullName}`;
    },
  },
  actions: {
    setFullName(firstName: string, familyName: string) {
      this.firstName = firstName;
      this.familyName = familyName;
    },
    //Promiseを返すコードもかける
    async loadUser() {
      const user = await requestHoge..//サーバーにリクエストしデータを取得

      this.firstName = user.firstName;
      this.familyName = user.familyName;
    },
  },
});
```

こんな感じになります。

`id` でストアを紐づけて管理しているようです。

既存の値をもとに算出したい場合は `getters`を使います。
`this`を使うことで参照ができるようです。

## コンポーネントから使う

```vue:QiitaSample.vue
<script lang="ts">
import { computed, defineComponent } from "vue";
import { useUserStore } from "./useUserStore";

export default defineComponent({
  name: "QiitaSample",
  setup() {
    const store = useUserStore();

    const firstName = computed(() => store.firstName);
    const familyName = computed(() => store.familyName);
    const fullName = computed(() => store.fullName);

    const setFullName = () => {
      store.setFullName("太郎", "キータ");
    };
    const loadUser = async () => {
      await store.loadUser();
    };
    return {
      firstName,
      familyName,
      fullName,
      setFullName,
      loadUser,
    };
  },
});
</script>
```

`setup` 関数の中で 先ほど定義した`useUserStore` を呼び出すことで使えるようになります。

### 値を参照する

```ts
//stateから
const firstName = computed(() => store.firstName);
const familyName = computed(() => store.familyName);
//gettersから
const fullName = computed(() => store.fullName);
```

それぞれ `computed` を使うことでできます。

### 値を変更する

変更については様々な方法が用意されているようです。

#### 直接代入

```ts
store.firstName = "太郎";
store.familyName = "キータ";
```

なんと、そのままストアに代入することもできます。

#### $patch を使う

```ts
store.$patch({
  firstName: "太郎",
  familyName: "キータ",
});
```

`$patch` を使う事でも状態を変更することができます。
`state` の変更したい部分だけ(例えば`firstName` のみ) を渡すこともできます。

```ts
store.$patch((state) => {
  state.firstName = "太郎";
  state.familyName = "キータ";
});
```

このように`state` を引数とする関数を受け取ることもできます。

#### $state で全部置き換える

```ts
store.$state = {
  firstName: "太郎",
  familyName: "キータ",
};
```

`$state` に代入することで `state` 全体を置き換えることもできます。

### action を実行する

```ts
const setFullName = () => {
  store.setFullName("太郎", "キータ");
};
```

そのまま呼び出すだけです。

```ts
const loadUser = async () => {
  await store.loadUser();
};
```

`Promise` を返すものは `await` を使うこともできます。

## 複数のストアを組み合わせる

```ts:useHogeStore.ts
import { defineStore } from "pinia";

export const useHogeStore = defineStore({
  id: "hoge",
  state: () => ({
    hogeName: "",
  }),
  getters: {
    enclosedHogeName() {
      return `[${this.hogeName}]`;
    },
  },
});
```

```ts:useFooStore.ts
import { defineStore } from "pinia";
import { useHogeStore } from "./useHogeStore";

export const useFooStore = defineStore({
  id: "foo",
  state: () => ({
    fooName: "",
  }),
  getters: {
    fooAndEnclosedHoge() {
      //ここでHogeStoreを参照する
      const hogeStore = useHogeStore();
      return `${this.fooName} ${hogeStore.enclosedHogeName}`;
    },
  },
});
```

`getter` の中で `useXxxStore` とすることで別のストアを参照できるそうです。

[コード](https://github.com/posva/pinia/blob/v2.0.0-alpha.10/src/store.ts#L325)を見た感じ `useXxxStore` するたびに `buildStoreToUse` が呼ばれているように見えますがパフォーマンスはどうなんでしょね 🤔

別のストアを一つだけ参照するならこんな感じで良いみたいですけど
互いにいろいろ参照する場合は専用のストアを作ったほうが良いとのこと
([このあたり](https://pinia.esm.dev/cookbook/composing-stores.html#composing-stores))

```ts:useSharedStore.ts
import { defineStore } from "pinia";
import { useFooStore } from "./useFooStore";
import { useHogeStore } from "./useHogeStore";

export const useSharedStore = defineStore({
  id: "shared",
  getters: {
    fooAndEnclosedHoge() {
      const hogeStore = useHogeStore();
      const fooStore = useFooStore();
      return `${fooStore.fooName} ${hogeStore.enclosedHogeName}`;
    },
  },
});
```

`action` も同様です。

## まだドキュメントに書いていない機能

### $reset

[こちら](https://github.com/posva/pinia/blob/v2.0.0-alpha.10/src/store.ts#L150)

ストアを初期状態にする関数のようです

### $subscribe

[こちら](https://github.com/posva/pinia/blob/v2.0.0-alpha.10/src/store.ts#L118)

`Vuex` の`subscribe` みたいなものかな？

`vuex-persisted-state` のようにストレージに保存したりする用途に使えるかもしれない。

今のところ `type` に絵文字が入ってますね😅

## さいごに
必要最小限の機能がそろっていて型もついているので便利そうな印象です。

正式リリースが楽しみですね