在加载组件之前等待 VueX 值加载
Wait for VueX value to load, before loading component
当用户尝试直接导航加载组件时 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
加载,然后再加载我的组件?
一种方法是将您的组件拆分为两个不同的组件。您的新父组件可以处理获取数据并在数据准备就绪后呈现子组件。
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
来延迟组件的渲染。
例如,如果您有一条名为 "photo" 的路线:
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 的例子(如果你不使用这个语法)。
您可以查看此页面上的 official documentation of beforeRouteEnter
,您还会发现您也可以使用 beforeEnter
将其放在路由级别。
一种方法是存储状态值。
例如,如果您的商店依赖单一 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.LOADING);
someFetchInterface()
.then(data=>commit('updateAccounts', data))
.catch(err=>commit('setApiState', ENUM.ERROR))
}
}
});
然后,通过添加一些 computed
变量,您可以切换显示哪个组件。使用state的好处是你可以轻松识别Error状态,并在状态未就绪时显示加载动画。
<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>
旁白... 我使用这种模式将我的应用程序作为状态机来管理。虽然此示例使用 vuex,但可以使用 Vue.observable
(vue2.6+) 或 ref
(vue3).
将其改编为在组件中使用
或者,如果您只是在商店中使用空数组 []
初始化 asyncListValues
,则可以避免需要数组的错误。
对我来说简单的方法:
...
watch: {
vuexvalue(newVal) {
if (newVal == 'XXXX')
this.loadData()
}
}
},
computed: {
...mapGetters(['vuexvalue'])
}
基于其他一些答案,如果您使用的是 Router,则可以通过仅在加载状态时调用 RouterView
来解决问题。
从@daniel 的方法开始,在加载状态时设置 stateLoaded
标志。我将在此处使用 two-state 标志使其保持简单,但您可以根据需要详细说明:
const store = createStore({
state () {
return {
mysettings: {}, // whatever state you need
stateLoaded: false,
}
},
mutations: {
set_state (state, new_settings) {
state.settings = new_settings;
state.stateLoaded = true;
},
}
}
然后,在 app.vue
中你会得到这样的东西:
<div class="content">
<RouterView/>
</div>
将此更改为:
<div class="content">
<RouterView v-if="this.$store.state.stateLoaded"/>
</div>
v-if
甚至不会尝试对 RouterView
做任何事情,直到(反应性)stateLoaded
标志变为 true
。因此,您使用 Router 渲染的任何内容都不会被调用,因此在加载时不会有任何未定义的状态变量。
您当然可以在此基础上添加 v-else
以显示“正在加载...”屏幕或其他内容,以防状态加载时间超过预期。使用@daniel 的 multi-state 标志,您甚至可以报告加载状态是否存在问题,并提供重试按钮或其他东西。
当用户尝试直接导航加载组件时 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
加载,然后再加载我的组件?
一种方法是将您的组件拆分为两个不同的组件。您的新父组件可以处理获取数据并在数据准备就绪后呈现子组件。
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
来延迟组件的渲染。
例如,如果您有一条名为 "photo" 的路线:
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 的例子(如果你不使用这个语法)。
您可以查看此页面上的 official documentation of beforeRouteEnter
,您还会发现您也可以使用 beforeEnter
将其放在路由级别。
一种方法是存储状态值。
例如,如果您的商店依赖单一 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.LOADING);
someFetchInterface()
.then(data=>commit('updateAccounts', data))
.catch(err=>commit('setApiState', ENUM.ERROR))
}
}
});
然后,通过添加一些 computed
变量,您可以切换显示哪个组件。使用state的好处是你可以轻松识别Error状态,并在状态未就绪时显示加载动画。
<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>
旁白... 我使用这种模式将我的应用程序作为状态机来管理。虽然此示例使用 vuex,但可以使用 Vue.observable
(vue2.6+) 或 ref
(vue3).
或者,如果您只是在商店中使用空数组 []
初始化 asyncListValues
,则可以避免需要数组的错误。
对我来说简单的方法:
...
watch: {
vuexvalue(newVal) {
if (newVal == 'XXXX')
this.loadData()
}
}
},
computed: {
...mapGetters(['vuexvalue'])
}
基于其他一些答案,如果您使用的是 Router,则可以通过仅在加载状态时调用 RouterView
来解决问题。
从@daniel 的方法开始,在加载状态时设置 stateLoaded
标志。我将在此处使用 two-state 标志使其保持简单,但您可以根据需要详细说明:
const store = createStore({
state () {
return {
mysettings: {}, // whatever state you need
stateLoaded: false,
}
},
mutations: {
set_state (state, new_settings) {
state.settings = new_settings;
state.stateLoaded = true;
},
}
}
然后,在 app.vue
中你会得到这样的东西:
<div class="content">
<RouterView/>
</div>
将此更改为:
<div class="content">
<RouterView v-if="this.$store.state.stateLoaded"/>
</div>
v-if
甚至不会尝试对 RouterView
做任何事情,直到(反应性)stateLoaded
标志变为 true
。因此,您使用 Router 渲染的任何内容都不会被调用,因此在加载时不会有任何未定义的状态变量。
您当然可以在此基础上添加 v-else
以显示“正在加载...”屏幕或其他内容,以防状态加载时间超过预期。使用@daniel 的 multi-state 标志,您甚至可以报告加载状态是否存在问题,并提供重试按钮或其他东西。