一、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和beforeEach
、afterEach
这些钩子,这里对比着讲更清楚:
钩子/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版本)
-
配置路由,使用异步组件
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') // 异步推荐组件 } ] } ]
-
在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 }) } } }
- 效果验证
- 页面首次加载时,onReady会等所有异步组件(Home、Banner、Recommend)加载完,再渲染页面,用户看不到“组件逐步加载导致的页面跳动”。
- 数据请求在路由就绪后发起,cityId能正确从路由参数中获取,避免请求参数错误。
记住这3个核心点
学完onReady,关键要记住:
- 版本差异:Vue2用
router.onReady(回调)
,Vue3用await router.isReady()
。 - 触发时机:只在路由初始化完成后执行一次,不是每次导航都执行。
- 适用场景:客户端首屏数据加载、SSR渲染、依赖路由状态的插件初始化。
只要把这几点吃透,再结合项目里的实际需求(比如优化首屏、处理SSR),就能把onReady用得顺手,避免常见的 bugs~
最后再唠叨一句:遇到路由相关的“时机问题”,先想想onReady/isReady能不能解决,很多时候它就是那个“恰到好处”的开关,让代码执行时机更精准~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。