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

一、vue router的isReady到底是个啥?

terry 8小时前 阅读数 10 #Vue
文章标签 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前端网发表,如需转载,请注明页面地址。

发表评论:

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

热门