Code前端首页关于Code前端联系我们

一、Vue Router onReady 是干啥的?

terry 11小时前 阅读数 11 #Vue
文章标签 Vue Router;onReady

做Vue项目时,你有没有遇到过“路由还没准备好,代码就开始执行”的情况?比如想在路由初始化后加载数据、初始化插件,结果要么逻辑提前跑了,要么页面闪一下才正常,这时候Vue Router的onReady(还有Vue Router 4里的isReady)就能派上用场,今天咱们就把onReady的用法、场景、踩坑点一次性讲透,不管是Vue2还是Vue3项目都能搞明白~

先从基础讲起——Vue Router里的onReady,是专门用来监听“路由初始化完成”的API,啥叫“路由初始化完成”?就是路由已经匹配好初始页面、执行完所有相关的导航钩子(比如全局前置守卫beforeEach、组件内的beforeRouteEnter这些),甚至连路由里的异步组件(比如用import()加载的组件)都加载完了,这时候onReady的回调才会执行。

举个例子:你打开Vue项目的页面,路由要先确定“用户当前该显示哪个页面”,还要处理页面里的异步组件、权限判断这些逻辑,onReady就像个“发令枪”,等这些事儿都干完了,再通知你“可以执行后续逻辑啦”。

这里得注意版本差异:

  • Vue Router 3(搭配Vue2):用的是router.onReady(callback),直接传回调函数,路由就绪后执行一次。
  • Vue Router 4(搭配Vue3):API改成了router.isReady(),返回一个Promise,所以要用.then()或者await来处理逻辑。

很多同学升级项目后报错,就是因为没注意这个版本变化!比如Vue3项目里还写router.onReady(),控制台就会提示“onReady is not a function”,这时候得换成isReady()才行~

哪些场景必须用onReady?

不是所有路由逻辑都要等onReady,但这3类场景不用它,代码容易出问题:

客户端首次渲染后的数据加载

比如首页要展示用户订单列表,得先拿到用户ID(可能存在路由参数里),如果不等路由就绪就发请求,路由参数还没解析,请求会带错参数,甚至直接报错,用onReady的话,就能保证“路由参数已经解析好、页面组件也准备好”之后,再发请求拿数据。

代码逻辑大概长这样(Vue Router 3为例):

const router = new VueRouter({
  routes: [/* 路由配置 */]
})
router.onReady(() => {
  // 路由就绪后,获取当前路由参数
  const { userId } = router.currentRoute.params
  // 发请求拿订单数据
  fetchOrders(userId)
})

服务端渲染(SSR)的收尾工作

做Vue SSR时,最大的挑战是“让服务端和客户端渲染的内容一致”,路由必须先完成初始化,确定好要渲染哪个页面、加载哪些异步组件,才能生成正确的HTML,这时候onReady(Vue3里是isReady)就是关键:

Vue SSR流程里,服务端代码大概这样写:

// 创建路由实例
const router = createRouter(/* 配置 */)
// 等待路由就绪
await router.isReady() 
// 路由就绪后,创建Vue实例、渲染成HTML
const app = createApp(App)
app.use(router)
const html = renderToString(app)

如果不等路由就绪就渲染,服务端可能没加载完异步组件,导致客户端 hydrated 时出现“内容不匹配”的错误,页面直接崩掉。

依赖路由状态的插件初始化

有些插件(比如权限控制、埋点统计工具)需要先知道“用户当前在哪个页面、有哪些路由权限”,才能正确初始化,比如权限插件要先读取路由里的meta.auth配置,再决定哪些页面能访问,这时候用onReady,就能保证插件初始化时,路由的配置和状态已经完全 ready:

// 权限插件逻辑
function initAuthPlugin(router) {
  router.onReady(() => {
    const authRules = router.options.routes.reduce((acc, route) => {
      if (route.meta?.auth) {
        acc[route.name] = route.meta.auth
      }
      return acc
    }, {})
    // 用authRules初始化权限逻辑
    setupAuth(authRules)
  })
}

手把手教你用onReady(分版本写)

前面说了版本差异,所以分Vue2(Router3)和Vue3(Router4)两种情况讲,避免大家踩坑~

Vue2 + Vue Router 3 版本

步骤1:创建路由实例

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from './views/Home.vue'
Vue.use(VueRouter)
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // 异步组件
    component: () => import('./views/About.vue')
  }
]
const router = new VueRouter({
  mode: 'history',
  routes
})
export default router

步骤2:调用onReady,写回调逻辑
在入口文件(比如main.js)里,等路由就绪后再挂载Vue实例:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
// 路由就绪后,再挂载App,避免页面闪屏
router.onReady(() => {
  new Vue({
    router,
    render: h => h(App)
  }).$mount('#app')
})

这样做的好处是:如果About页面是异步组件(需要加载),onReady会等About组件加载完,再渲染页面,用户就不会看到“组件还没加载完就显示空白”的情况。

Vue3 + Vue Router 4 版本

Vue Router 4把onReady改成了isReady(),返回Promise,所以用法更“Promise化”:

步骤1:创建路由实例(用createRouter

import { createRouter, createWebHistory } from 'vue-router'
import Home from './views/Home.vue'
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('./views/About.vue')
  }
]
const router = createRouter({
  history: createWebHistory(),
  routes
})
export default router

步骤2:用isReady()等待路由就绪
在main.js里,用async/await语法更简洁:

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
async function bootstrap() {
  // 等待路由就绪
  await router.isReady()
  // 路由就绪后再挂载App
  createApp(App).use(router).mount('#app')
}
bootstrap()

这里router.isReady()会等待“初始路由的异步组件加载完成、导航钩子执行完毕”,所以挂载App时,所有必要的组件和路由状态都准备好了,页面渲染更稳定。

onReady 和其他路由钩子有啥区别?

很多同学分不清onReady和beforeEachafterEach这些钩子,这里对比着讲更清楚:

钩子/API 触发时机 触发次数 适用场景
onReady/isReady 路由初始化完成后执行一次 只执行一次 首次加载时的初始化逻辑
beforeEach 每次导航前执行 每次导航都触发 全局权限判断、导航拦截
afterEach 每次导航后执行 每次导航都触发 修改、加载条结束
beforeRouteEnter 组件内,进入该组件前执行 进入组件时触发 组件级的数据预加载、权限判断

举个直观的例子:做“页面加载进度条”功能,afterEach适合“每次导航结束后隐藏进度条”,但onReady只在“整个应用第一次路由就绪后”执行,比如用来“初始化进度条插件”,如果把“隐藏进度条”写在onReady里,只有第一次导航会隐藏,之后的导航就不管用了——这就是没分清触发次数导致的问题!

用onReady最容易踩的5个坑

知道了用法和场景,还要避开这些常见错误,否则代码跑不起来或者有 Bug:

版本混淆:Vue3项目还在用onReady

Vue Router 4(Vue3)里已经没有onReady了,换成isReady(),如果代码里写:

router.onReady(() => { ... }) // Vue3项目里会报错!

必须改成:

router.isReady().then(() => { ... }) 
// 或者用async/await
await router.isReady()

以为onReady会“每次导航都执行”

onReady(包括isReady)只在路由初始化时执行一次,如果想“每次导航后都执行某个逻辑”,得用afterEach,比如有同学想“每次页面切换后都请求最新数据”,结果把逻辑写在onReady里,只有第一次生效,后面切换页面完全没反应——这就是没理解触发次数!

SSR中忘记等待路由就绪

做Vue SSR时,服务端代码必须等路由就绪后再渲染,否则会出现“服务端和客户端渲染内容不一致”(hydration error),比如下面的错误写法:

// 错误:没等路由就绪就渲染
const app = createApp(App)
app.use(router)
const html = renderToString(app) // 此时路由可能还没加载异步组件

正确写法必须加await

await router.isReady() // 等路由就绪
const app = createApp(App).use(router)
const html = renderToString(app)

异步组件加载没被onReady等待

有人担心:“路由里有异步组件,onReady会不会不等组件加载完就执行?” 其实Vue Router 3和4的onReady/isReady,都会等待初始路由的异步组件加载完成,比如路由配置里有component: () => import('./About.vue'),onReady的回调会等这个组件加载完再执行,所以不用担心“组件还没加载,逻辑就执行了”的问题。

在onReady里操作DOM,但组件还没渲染

onReady只保证“路由就绪”,不代表“组件已经渲染到页面”,比如想在onReady里获取某个DOM元素的高度,可能因为组件还没渲染,导致获取到null,这时候要结合Vue的$nextTick,等组件渲染完成后再操作DOM:

router.onReady(() => {
  // 路由就绪,但组件可能还没渲染
  this.$nextTick(() => {
    // 现在组件已经渲染,可以安全操作DOM
    const domHeight = document.getElementById('box').offsetHeight
  })
})

实战:用onReady优化电商首页首屏体验

光讲理论不够,结合真实场景看看onReady怎么用,比如做一个电商APP的首页,有轮播图、推荐商品、分类导航,还有很多异步组件(比如懒加载的广告组件、楼层组件),要解决“首屏加载时组件闪跳、数据请求提前发送”的问题。

需求分析

  • 首页有多个异步组件,需要等这些组件加载完再显示页面,避免“组件加载中页面乱跳”。
  • 首页数据(比如推荐商品)依赖路由参数(比如用户城市ID,在路由参数里),必须等路由就绪后再请求数据。

实现步骤(Vue2 + Router3版本)

  1. 配置路由,使用异步组件

    const routes = [
    {
     path: '/home/:cityId',
     name: 'Home',
     // 异步加载首页组件
     component: () => import('./views/Home.vue'),
     children: [
       {
         path: 'banner',
         name: 'Banner',
         component: () => import('./components/Banner.vue') // 异步Banner组件
       },
       {
         path: 'recommend',
         name: 'Recommend',
         component: () => import('./components/Recommend.vue') // 异步推荐组件
       }
     ]
    }
    ]
  2. 在onReady里初始化数据和加载状态
    在main.js里,等路由就绪后再挂载App,并且在Home组件里,等路由就绪后请求数据:

// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
// 路由就绪后再挂载,避免异步组件加载时页面闪屏
router.onReady(() => {
  new Vue({
    router,
    render: h => h(App)
  }).$mount('#app')
})
// Home.vue
export default {
  name: 'Home',
  data() {
    return {
      goodsList: [],
      cityId: ''
    }
  },
  created() {
    // 路由可能还没就绪,所以把数据请求放到onReady里
    this.$router.onReady(() => {
      this.cityId = this.$route.params.cityId
      this.fetchGoods()
    })
  },
  methods: {
    fetchGoods() {
      // 根据cityId请求推荐商品
      axios.get(`/api/goods?city=${this.cityId}`).then(res => {
        this.goodsList = res.data
      })
    }
  }
}
  1. 效果验证
  • 页面首次加载时,onReady会等所有异步组件(Home、Banner、Recommend)加载完,再渲染页面,用户看不到“组件逐步加载导致的页面跳动”。
  • 数据请求在路由就绪后发起,cityId能正确从路由参数中获取,避免请求参数错误。

记住这3个核心点

学完onReady,关键要记住:

  1. 版本差异:Vue2用router.onReady(回调),Vue3用await router.isReady()
  2. 触发时机:只在路由初始化完成后执行一次,不是每次导航都执行。
  3. 适用场景:客户端首屏数据加载、SSR渲染、依赖路由状态的插件初始化。

只要把这几点吃透,再结合项目里的实际需求(比如优化首屏、处理SSR),就能把onReady用得顺手,避免常见的 bugs~

最后再唠叨一句:遇到路由相关的“时机问题”,先想想onReady/isReady能不能解决,很多时候它就是那个“恰到好处”的开关,让代码执行时机更精准~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门