環境
- Vue.js - 3.2.13
- pinia - 2.0.28
- lodash.clonedeep - 4.5.0
結論
-
readonly storeの実態はProxy(MDN) -
ProxyのresetにはObject.assign()を使え(stackoverflow) -
Proxyはtargetへの参照を保持しているに過ぎない-
store内部でno readonlyなraw stateを定義する -
raw stateをresetする。 - 外部からの参照を許可する
stateはreadonlyにする
-
code 抜粋
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)は用意されているが、Option Stores(公式Doc)の書式では$reset()が定義される。- 但し、
readonlyには出来ない。(※最低でも私は実装方法を知らない)
- 但し、
- 逆に
Setup Store(公式Doc)の書式の場合は、$resetは定義されない為、呼び出し時にエラーになる。- 但し、
readonlyに出来る(※重要)
- 但し、
Option Store の例
export const useOptionStore = defineStore('option-store', {
state: () => ({ name: 'taro' }),
actions: {
setName(name) {
this.state.name = name;
},
},
});
Setup Store の例
export const useSetupStore = defineStore('setup-store', () => {
const state = {
name: ref('taro'),
};
const actions = {
setName(name) {
state.name = name;
},
};
return {
...state,
...actions,
}
});
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 になる。
よくある $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))
})
正しく動作する例
<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>
動作しない例
<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 全体
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,
};
});
リンク