You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在Vue视图组件中等待Vuex状态初始化完成?

如何让Vue组件等待Vuex中异步加载的状态完成后执行逻辑?

我明白你遇到的问题了——因为getGenreList是在应用初始化的created钩子中调用的,Home组件的mounted钩子执行时,这个异步请求可能还没完成,所以sections还是空数组;而当你跳转回来时,Vuex里的genres已经加载好了,所以能正常获取。你考虑用Watcher的思路其实是可行的,不过还有其他更直观的方案,我给你梳理几种:

方案一:使用Watcher监听sections变化(你的初始思路,完善版)

通过监听sections的变化,当它从空数组更新为有数据的数组时,再触发电影列表的请求。同时添加一个标记避免重复请求:

<script>
import { mapState } from 'vuex'
import api from '../api/api'
export default {
  name: 'home',
  data () { 
    return { 
      movies: null,
      hasLoadedMovies: false // 标记是否已加载过电影,防止重复请求
    } 
  },
  computed: { ...mapState({ sections: state => state.genres }) },
  watch: {
    sections(newVal) {
      // 仅当sections有数据且未加载过电影时执行
      if (newVal.length > 0 && !this.hasLoadedMovies) {
        this.fetchMoviesByGenres()
      }
    }
  },
  async mounted() {
    // 兼容mounted时sections已加载完成的情况(比如页面刷新后缓存数据)
    if (this.sections.length > 0) {
      await this.fetchMoviesByGenres()
    }
  },
  methods: {
    async fetchMoviesByGenres() {
      const moviesArray = await Promise.all(
        this.sections.map(section => api.getMoviesByGenre(section.id))
      )
      this.movies = moviesArray
      this.hasLoadedMovies = true
    }
  }
}
</script>

这个方案的优势是实现简单,能自动响应sections的更新,不管它是何时加载完成的。

方案二:在Vuex中添加加载状态标记(更推荐,全局可控)

在Vuex的state里新增一个genresLoading状态,标记分类列表是否正在加载,让组件能明确判断等待时机:

先修改Vuex的index.js:

export default new Vuex.Store({
  state: { 
    genres: [],
    genresLoading: false // 新增加载状态标记
  },
  mutations: {
    setGenreList (state, payload) { 
      state.genres = payload 
    },
    setGenresLoading(state, payload) {
      state.genresLoading = payload
    }
  },
  actions: {
    async getGenreList ({ commit }) {
      commit('setGenresLoading', true)
      try {
        const response = await api.getGenreList()
        commit('setGenreList', response)
      } catch (error) { 
        console.log(error) 
      } finally {
        commit('setGenresLoading', false) // 无论成功失败都标记加载完成
      }
    }
  }
})

再修改Home.vue:

<script>
import { mapState } from 'vuex'
import api from '../api/api'
export default {
  name: 'home',
  data () { 
    return { 
      movies: null,
      hasLoadedMovies: false
    } 
  },
  computed: { 
    ...mapState({ 
      sections: state => state.genres,
      genresLoading: state => state.genresLoading
    }) 
  },
  async mounted() {
    // 如果分类列表还在加载,等待加载完成
    if (this.genresLoading) {
      await new Promise(resolve => {
        const unwatch = this.$watch('genresLoading', (newVal) => {
          if (!newVal) {
            unwatch() // 停止监听
            resolve()
          }
        })
      })
    }
    // 确认有分类数据且未加载过电影时执行请求
    if (this.sections.length > 0 && !this.hasLoadedMovies) {
      await this.fetchMoviesByGenres()
    }
  },
  methods: {
    async fetchMoviesByGenres() {
      const moviesArray = await Promise.all(
        this.sections.map(section => api.getMoviesByGenre(section.id))
      )
      this.movies = moviesArray
      this.hasLoadedMovies = true
    }
  }
}
</script>

这个方案的好处是把加载状态全局化,其他组件如果也依赖分类列表,也能直接复用这个状态,逻辑更清晰可控。

方案三:路由层面添加守卫(适合必须加载完成才能进入页面的场景)

如果Home页面必须在分类列表加载完成后才能访问,可以用路由的beforeEnter守卫,确保数据准备好后再进入路由:

修改router/index.js:

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import store from '../store'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
    beforeEnter: async (to, from, next) => {
      // 如果分类列表为空,等待请求完成;否则直接进入
      if (store.state.genres.length === 0) {
        await store.dispatch('getGenreList')
      }
      next()
    }
  },
  // 其他路由...
]

const router = new VueRouter({
  routes
})

export default router

此时Home.vue的mounted可以简化:

<script>
import { mapState } from 'vuex'
import api from '../api/api'
export default {
  name: 'home',
  data () { return { movies: null } },
  computed: { ...mapState({ sections: state => state.genres }) },
  async mounted () {
    const moviesArray = await Promise.all(
      this.sections.map(section => api.getMoviesByGenre(section.id))
    )
    this.movies = moviesArray
  }
}
</script>

这种方式的优势是组件不用处理等待逻辑,但如果getGenreList请求较慢,用户会看到路由跳转卡顿,建议配合加载动画使用。

总结

你最开始考虑的Watcher方案完全可行,实现简单;如果项目中有多个组件依赖分类列表的加载状态,方案二更合适,扩展性更强;如果Home页面必须依赖分类数据才能展示,方案三的路由守卫也是不错的选择。

内容的提问来源于stack exchange,提问作者LundinCast

火山引擎 最新活动