readonly な state を保持する store に reset method を実装する(Pinia)

# 環境

* Vue.js - 3.2.13
* pinia - 2.0.28
* lodash.clonedeep - 4.5.0


# 結論

* `readonly store` の実態は `Proxy`([MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy))
* `Proxy`の`reset`には`Object.assign()`を使え（[stackoverflow](https://stackoverflow.com/questions/60877890/reset-a-javascript-proxy-object-to-default-values)）
* `Proxy`は`target`への参照を保持しているに過ぎない
  1. `store`内部で`no readonly`な`raw state`を定義する
  1. `raw state`を`reset`する。
  1. 外部からの参照を許可する`state`は`readonly`にする

## code 抜粋

```diff_javascript:stores/store.js
// ...
import cloneDeep from "lodash.clonedeep";
  // ...
  const target = {
    name: "taro",
    age: 18,
  };

  // (1.) raw state
  const state = reactive({ ...cloneDeep(target) });

  const actions = {
    reset() {
      // (2.) raw state をリセット
      Object.assign(state, { ...cloneDeep(target) });
      return this;
    },
  // ...
  return {
    // (3.) raw state を readonly にして公開
    ...toRefs(readonly(state)),
    ...actions,
  };
}
```


# 前提にある問題

* `$reset()`（[公式Doc](https://pinia.vuejs.org/core-concepts/state.html#resetting-the-state)）は用意されているが、`Option Stores`（[公式Doc](https://pinia.vuejs.org/core-concepts/#option-stores)）の書式では`$reset()`が定義される。
  * 但し、`readonly`には出来ない。（※最低でも私は実装方法を知らない）
* 逆に`Setup Store`（[公式Doc](https://pinia.vuejs.org/core-concepts/#setup-stores)）の書式の場合は、`$reset` は定義されない為、呼び出し時にエラーになる。
  * 但し、`readonly`に出来る(※重要)

```js:Option Store の例
export const useOptionStore = defineStore('option-store', {

  state: () => ({ name: 'taro' }),

  actions: {
    setName(name) {
      this.state.name = name;
    },
  },
});
```

```js:Setup Store の例
export const useSetupStore = defineStore('setup-store', () => {

  const state = {
    name: ref('taro'),
  };
  
  const actions = {
    setName(name) {
      state.name = name;
    },
  };

  return {
    ...state,
    ...actions,
  }
});
```

```js:readonly な store の例
export const useReadonlyStore = defineStore('readonly-store', () => {

  const state = {
    name: ref('taro'),
  };
  
  const actions = {
    setName(name) {
      state.name = name;
    },
  };

  return {
    ...toRefs(readonly(state)),
    ...actions,
  };
});
```

* readonly な state を保持する store に例として紹介されている`$reset method`では正しく処理できない
* 外部呼び出し可能な $reset method を override しているので、外部参照時に readonly なプロパティの変更(`pinia.$patch()`)で warning になる。

```js:よくある $reset() メソッドの override 案
// [Vue.js] main.js
import { createPinia } from 'pinia';
import cloneDeep from 'lodash.clonedeep';

const pinia = createPinia();

pinia.use(({ store }) => {
  const initialState = cloneDeep(store.$state)
  pinia.$reset = () => pinia.$patch(cloneDeep(initialState))
})
```

```vue:正しく動作する例
<template>
  <button @click="reset">reset</button>
</template>

<script>
import { useSetupStore } from "./stores/setup-store.js"; // セットアップストア

export default {
  setup(){
    const setupStore = useSetupStore();

    return {
        setupStore,
    };
  },

  methods:{
    reset(){
      this.setupStore.$reset(); // Success!!
    }
  }
}
</sctipr>
```

```vue:動作しない例
<template>
  <button @click="reset">reset</button>
</template>

<script>
import { useReadonlyStore } from "./stores/readonly-store.js"; // readonly なセットアップストア

export default {
  setup(){
    const readonlyStore = useReadonlyStore();

    return {
        readonlyStore,
    };
  },

  methods:{
    reset(){
      this.readonlyStore.$reset(); // Warning!!
    }
  }
}
</sctipr>
```

# 参考

## readonly store の reset code 全体

```javascript:stores/store.js
import { reactive, readonly, toRefs } from "vue";
import { defineStore } from "pinia";
import cloneDeep from "lodash.clonedeep";

export const useSampleStore = defineStore("sample-store", () => {
  // target state
  const target = {
    name: "taro",
    age: 18,
  };

  // raw state
  const state = reactive({ ...cloneDeep(target) });

  const actions = {
    // reset method
    reset() {
      Object.assign(state, { ...cloneDeep(target) });
      return this;
    },

    // setter
    setName(name) {
      state.name = name;
      return this;
    },

    // setter
    setAge(age) {
      state.age = age;
      return this;
    },
  };

  return {
    // readonly
    ...toRefs(readonly(state)),
    ...actions,
  };
});
```

## リンク
https://dev.to/the_one/pinia-how-to-reset-stores-created-with-functionsetup-syntax-1b74

https://stackoverflow.com/questions/71690883/pinia-reset-alternative-when-using-setup-syntax

https://stackoverflow.com/questions/60877890/reset-a-javascript-proxy-object-to-default-values