一、vue router的isReady到底是个啥?
不少用Vue开发项目的同学,在处理路由初始化、服务端渲染或者异步路由组件时,总会碰到“啥时候路由才算真准备好?怎么等路由就绪后再执行操作?”这类问题,这时候vue - router提供的isReady就成了关键工具,今天咱就围绕vue - router的isReady,从是什么、为啥用、怎么用这些角度拆解清楚~
简单说,isReady是vue - router(主要指Vue3对应的vue - router@4 + 版本)暴露的**异步方法**,调用后返回一个Promise,这个Promise会在“路由系统完成初始化”时resolve——这里的“初始化”包含路由配置解析、异步组件加载(比如用import()动态导入的组件)、全局/路由独享守卫里的异步操作(比如beforeEach中发请求拿权限)等所有异步流程走完。举个直观对比:路由导航守卫(比如beforeEach)是“拦截单次路由跳转”的钩子,而isReady关注的是整个路由实例从创建到完全可用的过程,打个比方,路由就像一列火车,isReady要等火车把所有车厢(异步组件)接好、轨道(路由配置)确认好、调度指令(守卫逻辑)执行完,才会通知“可以发车(执行后续操作)”。
啥场景下非得用isReady?
知道了isReady是“等路由就绪”的工具,那实际开发中哪些情况必须依赖它?分享三个典型场景:
服务端渲染(SSR)防止“水合错误”
做SSR项目(比如Nuxt.js底层逻辑,或者自己用Vue + express手写SSR)时,最头疼的就是服务端渲染结果和客户端hydration后结果不一致,原因很简单:服务端渲染时路由没完全就绪(比如异步组件还在加载、守卫里的异步请求没完成),导致服务端吐出的HTML和客户端后续渲染的结构对不上,浏览器控制台就会报hydration mismatch错误。
这时候isReady就是“定心丸”——服务端处理请求时,创建路由后调用await router.isReady()
,等路由所有异步流程走完,再用renderToString
生成HTML,这样客户端拿到的服务端HTML是“完全就绪”的状态,后续hydration时就不会打架。
客户端处理大量异步路由组件
现在很多项目为了首屏性能,会把路由组件拆成异步加载(用import()),但如果应用初始化时,直接mount Vue实例,很可能出现“路由组件还没加载完,页面先渲染了”的情况——要么组件区域空白,要么报错。
比如有个后台管理系统,首页、用户页、订单页全是异步组件,这时候用isReady控制应用挂载时机就很必要:等所有异步组件加载完、路由守卫执行完,再把App挂载到页面上,用户看到的就是完整界面,不会有闪屏或错误。
路由切换后执行全局逻辑(埋点、权限验证等)
有些业务需求是“每次路由切换完成后,上报页面访问数据”或者“验证用户权限是否足够访问新页面”,但路由切换过程中可能有异步操作(比如组件异步加载、守卫里的异步请求),如果没等这些完成就执行埋点,数据可能不准确(比如页面组件还没加载完,埋点就发了)。
isReady虽然主要针对“初始化阶段”,但结合对currentRoute
的监听,能确保每次路由切换后的所有异步流程走完再执行逻辑。
import { watch } from 'vue' import { useRouter } from 'vue - router' const router = useRouter() watch( () => router.currentRoute.value, async (newRoute) => { await router.isReady() // 等新路由的异步流程走完 console.log('页面就绪,发埋点:', newRoute.path) } )
isReady具体咋用?看代码例子更清楚!
光说概念太虚,结合客户端、服务端两个场景的代码,看isReady怎么落地:
场景1:客户端应用“等路由就绪再挂载”
假设项目里有很多异步路由组件,初始化时要等所有组件加载完再展示页面,代码结构如下:
// main.js import { createApp } from 'vue' import { createRouter, createWebHistory } from 'vue - router' import App from './App.vue' // 同步组件(首页) import Home from './views/Home.vue' // 异步组件(关于页、用户页) const About = () => import('./views/About.vue') const User = () => import('./views/User.vue') // 定义路由规则 const routes = [ { path: '/', component: Home }, { path: '/about', component: About }, { path: '/user', component: User } ] // 创建路由实例 const router = createRouter({ history: createWebHistory(), routes }) // 关键:等路由就绪后再挂载App router.isReady() .then(() => { const app = createApp(App) app.use(router) app.mount('#app') console.log('路由所有异步流程走完,应用成功挂载!') }) .catch(err => { console.error('路由初始化失败:', err) // 这里可以跳转到错误页,比如404 router.push('/error') })
这里核心逻辑是:先创建路由,等isReady的Promise resolve(所有异步组件加载、守卫执行完),再mount应用,这样用户打开页面时,看到的就是完整渲染好的组件,不会有“组件加载中”的闪烁或错误。
场景2:服务端渲染(SSR)结合isReady
自己手写SSR时(比如用express做服务端),必须确保服务端渲染的HTML和客户端hydration结果一致,这时候isReady要配合renderToString
使用:
// server.js(服务端入口) import { createApp } from 'vue' import { createRouter, createMemoryHistory } from 'vue - router' import { renderToString } from '@vue/server - renderer' import express from 'express' import App from './App.vue' // 同步/异步路由组件 import Home from './views/Home.vue' const About = () => import('./views/About.vue') const routes = [ { path: '/', component: Home }, { path: '/about', component: About } ] const server = express() // 处理所有GET请求 server.get('*', async (req, res) => { // 创建服务端路由实例(用memoryHistory模拟路由) const router = createRouter({ history: createMemoryHistory(), routes }) // 步骤1:让路由匹配当前请求的URL router.push(req.url) try { // 步骤2:等路由就绪(异步组件加载、守卫执行完) await router.isReady() // 步骤3:创建Vue应用并渲染成字符串 const vueApp = createApp(App) vueApp.use(router) const html = await renderToString(vueApp) // 步骤4:返回完整HTML给客户端 res.send(` <!DOCTYPE html> <html> <head><title>SSR Demo</title></head> <body>${html}</body> </html> `) } catch (err) { res.status(500).send('服务器内部错误') console.error('SSR失败:', err) } }) server.listen(3000, () => { console.log('服务端启动成功,端口3000') })
这个流程里,await router.isReady()
是“避免hydration错误”的核心——服务端必须等路由所有异步流程走完,再生成HTML,这样客户端拿到的HTML和后续hydration的结果才会一致。
用isReady时容易踩的“坑”和注意点
知道了用法,还要避开常见陷阱,这几个细节得留心:
isReady只在“初始化阶段”生效?
isReady返回的Promise,第一次resolve后,后续再调用会直接resolve,比如路由初始化完成后,你再调用router.isReady()
,它会立即返回已resolved的Promise,所以如果要监听“每次路由切换后的就绪状态”,不能只依赖isReady,得结合watch(currentRoute)
或者导航守卫。
和Vue Router 3.x的onReady有啥区别?
Vue Router 3.x(对应Vue2)里有个router.onReady(cb)
,作用和4.x的isReady类似,但调用方式不同:onReady是传入回调函数,而isReady返回Promise(更符合现代JS的异步编程习惯),如果是Vue2升级到Vue3的项目,要注意把onReady
换成isReady
。
异步组件加载失败,isReady会咋处理?
如果路由里的异步组件加载失败(比如网络问题导致import()报错),isReady返回的Promise会reject,所以必须用.catch()
捕获错误,否则整个应用会因为未捕获的Promise rejection崩溃,前面的代码例子里,都加了catch逻辑,就是为了处理这种情况(比如跳转到错误页,给用户提示)。
isReady是路由“全局就绪”的开关
绕了这么多,再提炼下:vue - router的isReady是协调路由初始化阶段所有异步操作的工具,不管是SSR场景下保证服务端和客户端渲染一致,还是客户端处理大量异步路由组件,它都能帮我们“等到路由真的准备好”再执行关键逻辑。
实际开发中,记住这几个核心点:
- 要等路由所有异步流程(组件加载、守卫执行)走完?用isReady;
- SSR项目里,服务端渲染前必须调isReady,否则容易hydration mismatch;
- 客户端初始化时,有异步路由组件就用isReady控制mount时机;
- 一定要处理isReady的reject情况,防止应用崩溃。
理解透isReady,不管是做高性能的SSR项目,还是优化客户端路由体验,都能更游刃有余~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。