在加载组件之前等待 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 标志,您甚至可以报告加载状态是否存在问题,并提供重试按钮或其他东西。