タイトルの通り、Vue3+Pinia環境でストア(Pinia)のデータ初期化と
Vueのライフサイクルが噛み合わなかった話をしようと思います。
前提
/DocRoot
|
+- components
| |
| +- RootComponent.vue
| |
| +- NestedComponent.vue
| |
| +- (And others. many nested components...)
|
+- stores
|
+ setting.js
|
+ sample.js
import { defineStore } from 'pinia'
export default defineStore('setting', {
state: () => ({
data: {},
}),
actions: {
async initialize() {
this.data = await fetch('https://example.com/api/setting')
}
}
}
import { defineStore } from 'pinia'
export default defineStore('sample', {
state: () => ({
data: {}, // settingストアから取得した値を設定
}),
}
<template></template>
<script>
import NestedComponent from './NestedComponent.vue'
import settingStore from '../../stores/setting'
export default {
components: {
NestedComponent
},
async created() {
const setting = settingStore()
setting.initialize()
},
}
</script>
<template></template>
<script>
import settingStore from '../../stores/setting'
import sampleStore from '../../stores/sample'
export default {
async beforeUpdate() {
const setting = settingStore()
const sample = sampleStore()
/*
* ここでsettingストアのデータが設定されることを期待しているが
* 実際には空オブジェクト{}が設定される
*/
sample.data = setting.data
}
}
</script>
何に困ったのか?
前提に記載の通り、親コンポーネントでsettingストアを初期化したつもりになっていましたが
子コンポーネントでsettingストアを参照した時には初期化が完了していない状態でした。
Vueは子コンポーネントから順番に描画していくなど、
コンポーネント間の順序も関係しているとは思います。
また、それとは別にVueの処理とPiniaのストア処理は別々に動いているものと推測されます。
では、一番ネストが深いコンポーネントでsettingストアを初期化すればいいかとも考えたのですが
一番ネストが深いコンポーネントはv-for
で複数描画しているため、
settingストアの初期化が複数回行われることになってしまいます。
初期化は1回にしたかったのでこの案は却下しました。
次に、settingストアの初期化をNestedComponent
で行うことを検討してみましたが
サンプルには記述がありませんが、settingストアをRootComponent
でも使用しているため
settingストアがどこで初期化しているのか分かりづらくなると考え却下しました。
試したこと・その1
Vueのライフサイクル(親コンポーネントのcreated
後に
子コンポーネントのbeforeUpdate
が走ることを期待していました。
この流れでいけば子コンポーネントのbeforeUpdate
が実行されるタイミングでは
settingストアが初期化されているはず!と。
そこで、以下のようにsettingストアにフラグを追加して、
NestedComponent
ではそれを判定して処理するように書き換えてみました。
import { defineStore } from 'pinia'
export default defineStore('setting', {
state: () => ({
data: {},
isInitialized: false,
}),
actions: {
async initialize() {
this.data = await fetch('https://example.com/api/setting')
this.isInitialized = true
}
}
}
<template></template>
<script>
import settingStore from '../../stores/setting'
import sampleStore from '../../stores/sample'
export default {
async beforeUpdate() {
const setting = settingStore()
const sample = sampleStore()
/*
* ここでsettingストアのデータが設定されることを期待しているが
* 実際には空オブジェクト{}が設定される
*/
if (setting.isInitialized) {
sample.data = setting.data
}
}
}
</script>
結果、うまくいきませんでした。
NestedComponent
のbeforeUpdate
が実行されるタイミングでは
まだ初期化が完了しておらず、その後、beforeUpdate
が実行されることもなく…
試したこと・その2
その1がダメだったので、いっそsettingストアで初期化を行なったタイミングで
sampleストアに値を設定してしまえ、と考えてみました。
import { defineStore } from 'pinia'
import sampleStore from './sample'
export default defineStore('setting', {
state: () => ({
data: {},
isInitialized: false,
}),
actions: {
async initialize() {
this.data = await fetch('https://example.com/api/setting')
const sample = sampleStore()
sample.data = this.data
}
}
}
これは期待通りに動作してくれたのですが、settingストアでsampleストアに値を設定しているので
コードの見通しが悪くなってしまいました。(実際にはsettingの一部をsampleに設定していたため)
試したこと・その3
fetch
がPromise
を返すことを利用して「その1」に近い発想で判定することを思いつき試しました。
import { defineStore } from 'pinia'
import sampleStore from './sample'
export default defineStore('setting', {
state: () => ({
data: {},
isInitialized: new Promise(() => {}),
}),
actions: {
initialize() {
this.isInitialized = fetch('https://example.com/api/setting')
}
}
}
<template></template>
<script>
import settingStore from '../../stores/setting'
import sampleStore from '../../stores/sample'
export default {
async beforeUpdate() {
const setting = settingStore()
const sample = sampleStore()
/*
* thenで初期化完了後に値を設定している
* awaitでここで一旦処理が完了するのを待つようにしている
*/
await setting.isInitialized.then(
sample.data = setting.data
)
}
}
</script>
結果、期待通りに動作してくれました!
もしかしたらNestedComponent
に関してはawait
だけで上手くいくのかもしれませんが
文脈的に伝わりやすいと思ったので、今回はこの書き方にしてみました。
結論
考えてみると当たり前のことなのですが、VueのライフサイクルとPiniaの初期化は全く別な処理なので
今回の事例のように、期待したタイミングで値が入っていないかもしれない、
ということを意識しないといけない、とまた一つ勉強になりました。
最終的にPromise
を用いたのはちょっとトリッキーかな、と思いつつも
非同期処理をうまく活用できるのはJavaScriptのメリットの一つなのかなと感じました。
普段はバックエンド(主にPHP)を主戦場としていると処理が上から順番に流れていくのが
当たり前になってしまっていて、この辺りは今後フロントエンドに携わるときにしっかり意識しないと
ということを実感しました。
同じようなことで困る人は少ないのかもしれませんが、誰かの参考になれば幸いです!
それではまたどこかで!