ユーザーがコンポーネントのURLを直接ナビゲートしようとすると、vuexアクションでhttp呼び出しが行われ、解決されると状態の値が定義されます。
Http呼び出しが解決され、状態値が定義されるまで、コンポーネントをロードしたくありません。
たとえば、私のコンポーネントで
export default {
computed: {
...mapState({
// ** this value needs to load before component mounted() runs **
asyncListValues: state => state.asyncListValues
})
},
mounted () {
// ** I need asyncListValues to be defined before this runs **
this.asyncListValues.forEach((val) => {
// do stuff
});
}
}
コンポーネントをロードする前に、コンポーネントがasyncListValues
のロードを待機するようにするにはどうすればよいですか?
それを行う方法は、状態値を格納することです。
たとえば、ストアが単一のAPIに依存している場合は、次のようにします。ただし、複数のAPIの場合、各APIのロード状態を個別に保存するか、各APIに専用のオブジェクトを使用することをお勧めします。
あなたが持つことができる通常4つの状態がありますが、私はグローバルにアクセス可能なモジュールに入れたいです:
// enums.js
export default {
INIT: 0,
LOADING: 1,
ERROR: 2,
LOADED: 3
};
次に、変数をvuex状態に格納し、apiStateをINIT
で初期化します。 []
で配列を初期化することもできますが、それは必要ありません。
import ENUM from "@/enums";
// store.js
export default new Vuex.Store({
state: {
apiState: ENUM.INIT,
accounts: [],
// ...other state
},
mutations: {
updateAccounts (state, accounts) {
state.accounts = accounts;
state.apiState = ENUM.LOADED;
},
setApiState (state, apiState) {
state.apiState = apiState;
},
},
actions: {
loadAccounts ({commit) {
commit('setApiState', ENUM.ERROR);
somFetchInterface()
.then(data=>commit('updateAccounts', data))
.catch(err=>commit('setApiState', ENUM.ERROR))
}
}
});
次に、computed
sを追加することで、表示するコンポーネントを切り替えることができます。これは、エラー状態を簡単に識別できるため、状態を使用する利点を示し、状態が準備できていないときに読み込みアニメーションを表示します。
<template>
<ChildComponent v-if="apiStateLoaded"/>
<Loader v-if="apiStateLoading"/>
<Error v-if="apiStateError"/>
</template>
<script>
import ENUM from "@/enums";
export default {
computed: {
...mapState({
apiState: state=> state.apiState
}),
apiStateLoaded() {
return this.apiState === ENUM.LOADED;
},
apiStateLoading() {
return this.apiState === ENUM.LOADING || this.apiState === ENUM.INIT;
},
apiStateError() {
return this.apiState === ENUM.ERROR;
},
})
}
</script>
または、ストアでasyncListValues
を空の配列[]
で初期化するだけで、配列を予期するエラーを回避できます。
1つのアプローチは、コンポーネントを2つの異なるコンポーネントに分割することです。新しい親コンポーネントは、データの準備ができたら、データのフェッチと子コンポーネントのレンダリングを処理できます。
ParentComponent.vue
<template>
<child-component v-if="asyncListValues && asyncListValues.length" :asyncListValues="asyncListValues"/>
<div v-else>Placeholder</div>
</template>
<script>
export default {
computed: {
...mapState({
asyncListValues: state => state.asyncListValues
})
}
}
</script>
ChildComponent.vue
export default {
props: ["asyncListValues"],
mounted () {
this.asyncListValues.forEach((val) => {
// do stuff
});
}
}
質問でvue-router
について言及したので、コンポーネントのレンダリングを延期するために作成されたbeforeRouteEnter
を使用できます。
たとえば、「写真」というルートがある場合:
import Photo from "../page/Photo.vue";
new VueRouter({
mode: "history",
routes: [
{ name: "home", path: "/", component: Home },
{ name: "photo", path: "/photo", component: Photo }
]
});
次のようにbeforeRouteEnter
を使用できます。
<template>
<div>
Photo rendered here
</div>
</template>
<script>
export default {
beforeRouteEnter: async function(to, from, next) {
try {
await this.$store.dispatch("longRuningHttpCall");
next();
} catch(exception) {
next(exception);
}
}
}
</script>
それが行うことは、アクションが完了するのを待ち、必要に応じて状態を更新してから、next()
を呼び出すと、ルーターがプロセスを続行するように指示されます(<router-view></router-view>
内のコンポーネントをレンダリングします)。
ES6を使用しない例が必要かどうか(たとえば、この構文を使用しない場合)を教えてください。
このページで beforeRouteEnter
の公式ドキュメント を確認できます。また、beforeEnter
を使用してルートレベルに配置することもできます。