目录安装原理popstate 事件监听 URL 变化Route 组件来匹配当前的 URL 路径后端路由前端路由history 模式前进后退、隐藏额外字符、无#、需服务器支持、html5刷新页面会发起请求404hash 模式:createHashRouter未指定路由模式自动选择本地开发和打包路由切换1.配置路由表的字段path、element/component、childrenVue命名路由name命名视图同级展示路由元信息meta任意附加信息如授权2.根据路由表生成路由对象createBrowserRouter3.在主入口/src/router/index.js 结合路由配置文件RouterProvider router{router}传参参数变更-组件更新query显式携带辅助信息params显式/隐式刷新页面会消失差异化界面分隔URL和参数#锚点Anchor标识文档中的特定位置或元素由浏览器处理,不发送到服务器指示浏览器滚动到具有 idsection1 的元素处location属性值window.location.href获取/设置 urlwindow.location.orgin协议、主机名和端口号部分window.location.protocol: 协议httpwindow.location.host主机端口host:8080/IP地址127.123.32.1唯一/域名www.example.com助记window.location.hostname主机hostwindow.location.port端口8080reactuseLocation()路由分类Link标签和a标签区别Link to/首页/Linka 原生超链接标签触发页面刷新不适合单页应用 (SPA)带样式的声明式路由NavLink动态路由(不同parmas访问同一个组件)编程式路由(非link模式)navigate useNavigate()默认路由没有匹配成功 、重定向路由redirect/Navigate 、404*/errorElementVueReactloader回调函数(路由前触发)主入口守卫权限、路由拦截导航解析流程全局VueReact/src/components/BeforeEach.jsx局部路由相比组件路由更推荐局部更好地逻辑分块组件路由浏览器输入URL展示路由对应组件Vue占位router-view\router-linkReact占位Outlet\Link路由跳转push保留历史记录可返回默认复用缓存的组件触发 activated配置keep-alive动态加载的组件注册 nameactivated配合恢复滚动位置等上次状态replace不保留适合登陆页面导航重复修改原型push、replace方法物理导航 vs Router应用导航守卫处理权限或数据保存安装Vue3搭配的是Vue Router4其他用法上和vue2没有太大变化//Vue.use() 方法进行安装和注册VueRouter插件 //自动调用插件对象中的 install 方法 Vue.use(VueRouter);npm install vue-routernpm i react-router-dom原理popstate事件监听 URL 变化Route组件来匹配当前的 URL 路径路由根据不同的url地址展示不同的内容SPA单页应用的路径管理器后端路由路由分类前端路由后端路由页面位置SPA单页面应有服务器端更改url不会重新请求局部刷新重新请求SEO搜索引擎优化大部分内容 JS 动态生成的更利于 SEO前端路由作为单页面应用SPA首次请求会把前端所需的页面下载下来不利于SEO优化所有页面的跳转都是在客户端进行操作监听URL变化从而对页面进行渲染不会刷新页面即不会http请求hash 本质是锚点定位, 跳到已加载页面的指定位置history会对浏览器进行history栈操作history模式前进后退、隐藏额外字符、无#、需服务器支持、html5history模式使用浏览器的 History API可以在 URL 中隐藏额外的字符适合大多数常规的浏览器环境。不使用哈希符号#路由更加美观。可以充分利用浏览器的前进和后退功能。需要服务器配置支持以确保在直接访问路由时返回正确的页面。HTML5的history API监听URL变化所以有浏览器兼容问题//Vue 2.x 或 3.x Options API const router new VueRouter({ mode: history, routes }); // Vue 3.x Composition API import { createRouter, createWebHistory } from vue-router; const router createRouter({ history: createWebHistory(), routes })刷新页面会发起请求404原因当用户在浏览器中刷新页面时浏览器会向服务器发送请求但服务器不知道如何处理这个特定的路由因为它可能对应的是前端应用中的某个路由路径而不是实际的文件路径。解决所有的路由请求(url变化都被指向前端应用的入口文件index.html对比hash模式不会出现url变化是因为#后的部分不会发送到服务端//服务器是 Nginx server { listen 80; server_name yourdomain.com; location / { try_files $uri $uri/ /index.html; # 尝试查找实际文件如果没有找到则返回index.html } # 可以根据实际情况配置其他路由 # location /about { # try_files $uri $uri/ /index.html; # } # 可以根据实际情况配置静态文件路径 # location /static { # alias /path/to/your/static/files; # } }hash模式:createHashRouterhash模式在 URL 中使用哈希值#可以通过监听哈希变化来实现路由导航不需要服务器配置支持哈希值不会发送到服务器即不会发送请求适用于不支持 History API 或需要兼容旧版浏览器的情况。不充分利用浏览器的前进和后退功能只有一个历史记录入口。通过window.addEventListener监听浏览器的onhashchange()事件变化查找对应的路由规则在单页应用SPA中#后面的部分是前端路由如果参数放在hash后面后端无法直接获取这些参数只有前端JavaScript可以通过window.location.hash获取错误参数在hash后面#/path?params- 后端收不到参数http://www.xxx.com/#/conversationList?date2025-08-07roleId0正确参数在hash前面?params#/path- 前后端都能正常处理http://www.xxx.com.com/?date2025-08-07roleId0#/conversationList//Vue 2.x 或 3.x Options API const router new VueRouter({ mode: hash, routes }); // Vue 3.x Composition API import { createRouter, createWebHashHistory} from vue-router; const router createRouter({ history: createWebHashHistory(), routes })未指定路由模式自动选择如果浏览器支持HTML5 History API即支持 History 模式并且应用部署在支持此模式的服务器上能够处理单页应用的所有路径请求Vue Router会自动选择使用History模式。这样可以创建更具语义的 URL而且在切换路由时不会显示#符号。不支持 HTML5 History API 或者部署环境不适用 History 模式: 如果浏览器不支持 HTML5 History API 或者应用部署在无法处理 History 模式的服务器上Vue Router 会自动回退到使用Hash模式。const router new VueRouter({ routes })本地开发和打包http://0.0.0.0:8081为本地启动http://www.baidu.com.index.html为打包的地址路由一个是加/#/selectRobot一个是加#/selectRobot本地地址多了一个/ http://0.0.0.0:8081/#/selectRobothttp://www.baidu.com.index.html#/selectRobot这两个URL都带#说明它们都是Hash模式。它们的核心区别不在于模式而在于服务器上的文件路径结构。本地服务器把index.html放在了根路径/而你的打包文件xxxx.index.html本身就是一个放在服务器根路径下的、名字很长的HTML文件。当你访问http://0.0.0.0:8081时本地服务器默认会返回根目录下的index.html文件。等价于访问http://0.0.0.0:8081/index.html#/selectRobot只是省略了index.html。特性Hash 模式History 模式URL 示例http://www.baidu.com.index.html#/selectRobothttp://0.0.0.0:8081/selectRobot符号使用#(hash)使用/(真实路径)原理利用#后的内容变化不会触发浏览器刷新或向服务器发送请求的特性。利用 HTML5 History API (pushState,replaceState) 来改变URL不会真正刷新页面。服务器要求无特殊要求。因为#后的内容服务器会忽略始终返回index.html。需要特殊配置。因为像/selectRobot这样的路径是一个真实的URL如果服务器没有正确配置访问它会返回404错误。美观度不美观URL中带#。美观看起来和普通的URL一样。部署难度简单非常适合静态资源服务器。稍复杂需要在服务器端做回退处理Fallback即所有未匹配到静态资源的请求都返回index.html。HTTP协议和Web服务器发送请求自动补全使空路径默认指向根路径如url末尾带/不会影响两种模式最简单的路由区别举例假设我们有一个项目有两个页面首页/和一个关于页/about。1. Hash 模式 (默认模式)首页:http://yourdomain.com/your-project/#/关于页:http://yourdomain.com/your-project/#/about浏览器实际请求的始终是http://yourdomain.com/your-project/index.html这个文件。#/about的变化由前端的 Vue Router 自己处理用于决定具体显示哪个组件。2. History 模式首页:http://yourdomain.com/your-project/关于页:http://yourdomain.com/your-project/about浏览器在首次加载时会请求http://yourdomain.com/your-project/index.html。当点击跳转到“关于页”时Vue Router 会通过 History API无刷新地将地址栏变成/about。但是如果你直接在这个地址栏输入http://yourdomain.com/your-project/about并回车浏览器会向服务器发起一个真实的 HTTP 请求请求/about这个路径下的资源。如果服务器如 Nginx, Apache没有配置好找不到这个路径对应的文件就会返回404 Not Found。路由切换1.配置路由表的字段path、element/component、childrenpath指定路径elementReact/component(Vue)对应组件children嵌套路由export default dataConfirmChildrenVue命名路由nameparams 的自动编码/解码{ path: /about/foo/:id, name: foo, component: Foo } { name: foo, params: {id: 123} }命名视图同级展示路由元信息meta任意附加信息如授权meta: { auth: false } this.$route.meta.auth import { useLocation, matchRoutes, Navigate } from react-router-dom import { routes } from ../../router; export default function BeforeEach(props) { const location useLocation(); const matchs matchRoutes(routes, location) const meta matchs[matchs.length-1].route.meta if(meta.auth){ return Navigate to/login / } else{ return ( div{ props.children }/div ) } }2.根据路由表生成路由对象createBrowserRouterimport { createBrowserRouter, createHashRouter } from react-router-dom //路由表 export const routes []; //路由对象 const router createBrowserRouter(routes); export default router;3.在主入口/src/router/index.js结合路由配置文件RouterProvider router{router}import { RouterProvider } from react-router-dom import router from ./router; const root ReactDOM.createRoot(document.getElementById(root)); root.render( React.StrictMode RouterProvider router{router}/RouterProvider /React.StrictMode );传参行为Query 参数Params 参数 (未定义在 path)表现形式留在 URL 中 (?urlxxx)存在内存中URL 不可见按 F5 刷新页面保留从地址栏重新解析丢失内存被清空组件不重载时的复用需依靠watch或:key驱动更新需依靠watch驱动更新Vue Router 4 (Vue 3 取消 无path匹配的 params 传参 特性是因为这种“纯内存参数”破坏了 Web 网页的核心原则——状态可预测性。正常的 URL 应该做到“所见即所得”你把当前地址栏的链接复制发给你的同事他打开后看到的界面应该和你一模一样。但如果允许隐式传递内存 params你同事打开这个链接时因为内存里没有数据页面就会白屏或报错。这种“刷新就崩、分享就死”的特性成了很多前端新手的 Bug 重灾区。参数变更-组件更新当 URL 里的 query 从?id1变成?id2时组件实例是同一个。如果你希望组件对这个变化做出响应标准姿势有两个姿势 A全量刷新最省心在router-view上绑定一个唯一的 key逼迫 Vue 只要路由变了就重新实例化组件。router-view :key$route.fullPath/router-view姿势 B定点监听性能好使用watch捕获特定参数的变化手动触发局部数据的重新请求。watch: { $route.query.id(newId, oldId) { // 只要 id 变了我就重新拉取接口不用整页销毁 this.fetchData(newId); } }query显式携带辅助信息path: /user/,$route.queryquery显式 /workbenchConfiguration/upload?wpReleId59import { useSearchParams } from react-router-dom const [searchParams, setSearchParams] useSearchParams() console.log( searchParams.get(age) ); const handleClick () { setSearchParams({ age: 22 }) } // 1. 传参跳转 ➡️ 生成的 URL 为: /user?urlhttps://abc.comtype1 this.$router.push({ name: User, query: { url: https://abc.com, type: 1 } }); // 2. 显式清除某个参数 ➡️ 地址栏会抹除 url只留下 /user?type1 this.$router.push({ query: { ...this.$route.query, url: undefined } });params显式/隐式刷新页面会消失差异化界面$route.params显式path: /user/:id,隐式path: /user/,query显式 /workbenchConfiguration/upload?wpReleId59params显式 /workbenchConfiguration/upload/59如果不想让数据挂在 URL 上又想刷新不丢失的敏感参数最标准的做法是配合sessionStorage或状态管理const routes [ { // :id 就是占位符如果加个 ? (如 :id?) 代表这个参数是可选的 path: /user/:id, name: UserDetails, component: UserDetailsComponent } ] // 1. 传参跳转 ➡️ 生成的 URL 为: /user/9527 (数据绑在 URL 中刷新绝不丢) // 【注意】使用 params 必须搭配 name 属性跳转不能直接写 path this.$router.push({ name: UserDetails, params: { id: 9527 } }); // 2. 目标组件中接收 console.log(this.$route.params.id); // 输出: 9527分隔URL和参数http://example.com/page?param1value1param2value2#section1#锚点Anchor标识文档中的特定位置或元素由浏览器处理,不发送到服务器指示浏览器滚动到具有idsection1的元素处location属性值window的全局对象表示当前页面http://www.example.com/path/index.htmlwindow.location.href获取/设置 urlwindow.location.orgin协议、主机名和端口号部分//https://www.example.com:8080/page.html // :// : //https%3A%2F%2Fwww.example.com%3A8080。 encodeURIComponent(window.location.origin) //encodeURIComponent用于将字符串中的特殊字符(空格、、、 、?)转换为编码形式确保URL中不包含任何无效字符 //查询参数时 或者 动态参数时 需要encodeURIComponent const url https://example.com/api?param encodeURIComponent(queryParam); window.location.href https://www.example.com/path/to/resource.html/domain${location.host}req${encodeURIComponent(location.pathname)}protocolhttps${location.hash}window.location.protocol: 协议httpwindow.location.host主机端口host:8080/IP地址127.123.32.1唯一/域名www.example.com助记window.location.hostname主机hostwindow.location.port端口8080window.location.pathname: 资源路径path/index.html资源index.htmlwindow.location.hash:window.location.search: 搜索var searchParams new URLSearchParams(window.location.search); console.log(searchParams.get(name)); // 输出 JohnreactuseLocation()import { useLocation } from react-router-domhash哈希值key唯一标识pathname路径searchquery值(需要把字符串解析成对象。)state隐式数据路由分类Link标签和a标签区别Link to/首页/Linka原生超链接标签触发页面刷新不适合单页应用 (SPA)带样式的声明式路由NavLink动态路由(不同parmas访问同一个组件)import { Outlet, Link } from react-router-dom export default function About() { return ( div Link to/about/foo/123foo 123/Link | Link to/about/foo/456foo 456/Link /div ) } //... { path: foo/:id, element: Foo / } //... import { useParams } from react-router-dom export default function Foo() { const params useParams() return ( divFoo, { params.id }/div ) } //Vue与React唯一不同 this.$route.params.id编程式路由(非link模式)navigate useNavigate()//vue this.$router.push({ path: /about/foo/${id} }) //react import {useNavigate } from react-router-dom const navigate useNavigate() const handleClick () { navigate(/about/foo/123) }默认路由没有匹配成功 、重定向路由redirect/Navigate 、404*/errorElementVueimport VueRouter from vue-router import Home from /views/Home.vue const About{template:divAbout/div} const routes: Arrayany [ { path: /, redirect: /workbenchConfiguration }, { path: /404, meta: { title: 404 }, component: () import(/views/404.vue) }, { path: *, redirect: /404 } ] const router new VueRouter({ routes })Reactimport { createBrowserRouter, createHashRouter } from react-router-dom //路由表 export const routes [ // 默认路由 { index: true, //重定向 element: Navigate to/about/foo/123 /, //自带errorElement errorElement: div404/div, }, //*局部404 { path: *, element: div404/div } ]; //路由对象 const router createBrowserRouter(routes); export default router;loader回调函数(路由前触发)默认同步配合redirect做权限拦截。{ path: bar, element: Bar /, //asyncawait异步Promise用于表示一个异步操作的最终完成或失败及其结果值。 loader: async() { let ret await new Promise((resolve){ setTimeout((){ resolve({errcode: 0}) }, 2000) }) return ret; } }useLoaderData()获取loader函数返回的数据import { useLoaderData } from react-router-dom export default function Bar() { const data useLoaderData() console.log(data) return ( divBar/div )loader函数中是没有办法使用Navigate组件进行重定向操作的{ path: bar, element: Bar /, loader: async() { let ret await new Promise((resolve){ setTimeout((){ resolve({errcode: Math.random() 0.5 ? 0 : -1}) }, 2000) }) if(ret.errcode 0){ return ret; } else{ return redirect(/login) } } }主入口//index.js import { RouterProvider } from react-router-dom import router from ./router; const root ReactDOM.createRoot(document.getElementById(root)); root.render( React.StrictMode RouterProvider router{router}/RouterProvider /React.StrictMode );守卫权限、路由拦截权限/拦截一个页面很多路由SPA登录后都能访问有一个页面需要额外认证导航解析流程导航被触发。在失活的组件里调用beforeRouteLeave守卫。调用全局的beforeEach守卫。在重用的组件里调用beforeRouteUpdate守卫(2.2)。在路由配置里调用beforeEnter解析异步路由组件。在被激活的组件里调用beforeRouteEnter。调用全局的beforeResolve守卫(2.5)。导航被确认。调用全局的afterEach钩子。触发 DOM 更新。调用beforeRouteEnter守卫中传给next的回调函数创建好的组件实例会作为回调函数的参数传入。全局Vue//to router.beforeEach((to, from, next){ if(to.meta.auth){ next(/); } else{ next(); } }) //vuets router.beforeEach((to: any, from: any, next: any) { const metaTitlt (to.meta to.meta.title) || document.title ${metaTitlt} - 默认模版 //是否从根路径而来当前路由的来源路径和即将进入的路由的路径是否相同 if (from.path ! / from.matched[0].path ! to.matched[0].path) { message.destroy() } next() })React/src/components/BeforeEach.jsximport React from react import { Navigate } from react-router-dom import { routes } from ../../router; export default function BeforeEach(props) { if(true){ return Navigate to/login / } else{ return ( div{ props.children }/div ) } }export const routes [ { path: /, element: BeforeEachApp //BeforeEach//包裹根组件APP } ]局部路由相比组件路由更推荐局部更好地逻辑分块const routes [ { name: bar, component: Bar, beforeEnter(to, from, next){ if(to.meta.auth){ next(/); } else{ next(); } } } ];组件路由script export default { name: FooView, beforeRouteEnter(to, from, next){ if(to.meta.auth){ next(/); } else{ next(); } } } /script浏览器输入URL展示路由对应组件没有占位的话默认整个页面Vue占位router-view\router-linktemplate div router-link to/首页/router-link | router-link to/about关于/router-link router-view/router-view /div /templateReact占位Outlet\Linkimport React from react; import { Outlet, Link } from react-router-dom function App() { return ( div classNameApp h2hello react/h2 Link to/首页/Link | Link to/about关于/Link Outlet / /div ); } export default App;带样式的声明式路由NavLink路由跳转push保留历史记录可返回将新的路由添加到浏览器的历史记录中这样用户就可以通过浏览器的后退按钮回到之前的路由。this.$router.push(/about)默认复用缓存的组件触发 activated只有当你使用了 keep-alive 标签包裹了 router-view并且在 SPA单页应用内部切走又切回来时组件没有被销毁才会触发 activated()激活和 deactivated()停用。配置{ path: /main/meet, name: meet, component: (resolve: any) require([/views/serviceCenter/index.vue], resolve), // iframeComponent: meet },keep-alivemain/index.vuekeep-alive includetodoTask,serviceCenter router-view/router-view /keep-alivekeep-alive include是根据组件的name选项来匹配的而不是路由的name。动态加载的组件注册name如果组件是动态加载的如() import(/views/meet)确保导出的组件有name属性export default { name: serviceCenter,activated配合恢复滚动位置等上次状态activated() { this.focusInput(delay); this.restoreScrollPosition(); }, beforeRouteLeave(to, from, next) { next(); this.scrollTop { history: document.querySelector(.chat_history_box).scrollTop || 0, panel: document.querySelector(.drawer_body).scrollTop || 0, }; },replace不保留适合登陆页面登录成功后可能会用replace方法替换当前路由以防止用户通过后退按钮回到登录页面。this.$router.replace(/contact)可能不会触发 activated特别是如果key变化或组件未正确缓存。导航重复修改原型push、replace方法修改 VueRouter 的原型方法push和replace用来捕获导航重复错误并进行处理而不会在控制台中抛出错误从而避免了不必要的错误提示和潜在的问题。import Vue from vue; import VueRouter from vue-router; Vue.use(VueRouter); const originalPush VueRouter.prototype.push; VueRouter.prototype.push function push(location) { return originalPush.call(this, location).catch(err { if (err.name ! NavigationDuplicated) { throw err; } }); }; const originalReplace VueRouter.prototype.replace; VueRouter.prototype.replace function replace(location) { return originalReplace.call(this, location).catch(err { if (err.name ! NavigationDuplicated) { throw err; } }); }; const router new VueRouter({ // 路由配置... }); export default router;物理导航vs Router维度浏览器按钮键盘/移动端手势前进后退Router 前端路由操作本质浏览器行为应用内行为控制权由浏览器历史记录栈控制由前端路由库Vue Router/React Router控制URL 变化URL 改变并向服务端发起请求若未处理仅改变URL 的路径部分无服务端请求页面动作完整页面刷新默认行为会重建DOMSPA 组件切换仅更新部分视图无刷新数据状态页面状态丢失JS 内存被清空页面状态保留在 SPA 内性能差网络请求、重新解析渲染好本地JS执行瞬时切换开发者干预通过popstate事件监听可阻止默认行为完全可控可添加导航守卫、动画等底层API操作的是window.history对象。核心事件popstate事件。当历史记录条目改变时用户执行前进/后退会触发此事件。// 监听浏览器前进后退 window.addEventListener(popstate, (event) { console.log(位置变了:, window.location.pathname); // 可以在这里同步前端路由的状态但无法阻止手势本身 });触发条件在应用内点击router-link或调用router.push()/router.go()。底层原理通过history.pushState()/history.replaceState()API修改历史栈但不触发页面刷新。// Vue Router 示例 this.$router.push(/next-page); // 添加一条记录并跳转 this.$router.go(-1); // 向前移动一条记录等同于浏览器后退 this.$router.go(1); // 向后移动一条记录等同于浏览器前进应用导航守卫处理权限或数据保存// Vue Router 示例在离开页面前保存数据 beforeRouteLeave(to, from, next) { if (this.formChanged) { if (confirm(内容未保存确定离开)) { next(); } else { next(false); // 取消导航 } } else { next(); } }
vue和React路由、history、hash模式,缓存activated、keep-alive
目录安装原理popstate 事件监听 URL 变化Route 组件来匹配当前的 URL 路径后端路由前端路由history 模式前进后退、隐藏额外字符、无#、需服务器支持、html5刷新页面会发起请求404hash 模式:createHashRouter未指定路由模式自动选择本地开发和打包路由切换1.配置路由表的字段path、element/component、childrenVue命名路由name命名视图同级展示路由元信息meta任意附加信息如授权2.根据路由表生成路由对象createBrowserRouter3.在主入口/src/router/index.js 结合路由配置文件RouterProvider router{router}传参参数变更-组件更新query显式携带辅助信息params显式/隐式刷新页面会消失差异化界面分隔URL和参数#锚点Anchor标识文档中的特定位置或元素由浏览器处理,不发送到服务器指示浏览器滚动到具有 idsection1 的元素处location属性值window.location.href获取/设置 urlwindow.location.orgin协议、主机名和端口号部分window.location.protocol: 协议httpwindow.location.host主机端口host:8080/IP地址127.123.32.1唯一/域名www.example.com助记window.location.hostname主机hostwindow.location.port端口8080reactuseLocation()路由分类Link标签和a标签区别Link to/首页/Linka 原生超链接标签触发页面刷新不适合单页应用 (SPA)带样式的声明式路由NavLink动态路由(不同parmas访问同一个组件)编程式路由(非link模式)navigate useNavigate()默认路由没有匹配成功 、重定向路由redirect/Navigate 、404*/errorElementVueReactloader回调函数(路由前触发)主入口守卫权限、路由拦截导航解析流程全局VueReact/src/components/BeforeEach.jsx局部路由相比组件路由更推荐局部更好地逻辑分块组件路由浏览器输入URL展示路由对应组件Vue占位router-view\router-linkReact占位Outlet\Link路由跳转push保留历史记录可返回默认复用缓存的组件触发 activated配置keep-alive动态加载的组件注册 nameactivated配合恢复滚动位置等上次状态replace不保留适合登陆页面导航重复修改原型push、replace方法物理导航 vs Router应用导航守卫处理权限或数据保存安装Vue3搭配的是Vue Router4其他用法上和vue2没有太大变化//Vue.use() 方法进行安装和注册VueRouter插件 //自动调用插件对象中的 install 方法 Vue.use(VueRouter);npm install vue-routernpm i react-router-dom原理popstate事件监听 URL 变化Route组件来匹配当前的 URL 路径路由根据不同的url地址展示不同的内容SPA单页应用的路径管理器后端路由路由分类前端路由后端路由页面位置SPA单页面应有服务器端更改url不会重新请求局部刷新重新请求SEO搜索引擎优化大部分内容 JS 动态生成的更利于 SEO前端路由作为单页面应用SPA首次请求会把前端所需的页面下载下来不利于SEO优化所有页面的跳转都是在客户端进行操作监听URL变化从而对页面进行渲染不会刷新页面即不会http请求hash 本质是锚点定位, 跳到已加载页面的指定位置history会对浏览器进行history栈操作history模式前进后退、隐藏额外字符、无#、需服务器支持、html5history模式使用浏览器的 History API可以在 URL 中隐藏额外的字符适合大多数常规的浏览器环境。不使用哈希符号#路由更加美观。可以充分利用浏览器的前进和后退功能。需要服务器配置支持以确保在直接访问路由时返回正确的页面。HTML5的history API监听URL变化所以有浏览器兼容问题//Vue 2.x 或 3.x Options API const router new VueRouter({ mode: history, routes }); // Vue 3.x Composition API import { createRouter, createWebHistory } from vue-router; const router createRouter({ history: createWebHistory(), routes })刷新页面会发起请求404原因当用户在浏览器中刷新页面时浏览器会向服务器发送请求但服务器不知道如何处理这个特定的路由因为它可能对应的是前端应用中的某个路由路径而不是实际的文件路径。解决所有的路由请求(url变化都被指向前端应用的入口文件index.html对比hash模式不会出现url变化是因为#后的部分不会发送到服务端//服务器是 Nginx server { listen 80; server_name yourdomain.com; location / { try_files $uri $uri/ /index.html; # 尝试查找实际文件如果没有找到则返回index.html } # 可以根据实际情况配置其他路由 # location /about { # try_files $uri $uri/ /index.html; # } # 可以根据实际情况配置静态文件路径 # location /static { # alias /path/to/your/static/files; # } }hash模式:createHashRouterhash模式在 URL 中使用哈希值#可以通过监听哈希变化来实现路由导航不需要服务器配置支持哈希值不会发送到服务器即不会发送请求适用于不支持 History API 或需要兼容旧版浏览器的情况。不充分利用浏览器的前进和后退功能只有一个历史记录入口。通过window.addEventListener监听浏览器的onhashchange()事件变化查找对应的路由规则在单页应用SPA中#后面的部分是前端路由如果参数放在hash后面后端无法直接获取这些参数只有前端JavaScript可以通过window.location.hash获取错误参数在hash后面#/path?params- 后端收不到参数http://www.xxx.com/#/conversationList?date2025-08-07roleId0正确参数在hash前面?params#/path- 前后端都能正常处理http://www.xxx.com.com/?date2025-08-07roleId0#/conversationList//Vue 2.x 或 3.x Options API const router new VueRouter({ mode: hash, routes }); // Vue 3.x Composition API import { createRouter, createWebHashHistory} from vue-router; const router createRouter({ history: createWebHashHistory(), routes })未指定路由模式自动选择如果浏览器支持HTML5 History API即支持 History 模式并且应用部署在支持此模式的服务器上能够处理单页应用的所有路径请求Vue Router会自动选择使用History模式。这样可以创建更具语义的 URL而且在切换路由时不会显示#符号。不支持 HTML5 History API 或者部署环境不适用 History 模式: 如果浏览器不支持 HTML5 History API 或者应用部署在无法处理 History 模式的服务器上Vue Router 会自动回退到使用Hash模式。const router new VueRouter({ routes })本地开发和打包http://0.0.0.0:8081为本地启动http://www.baidu.com.index.html为打包的地址路由一个是加/#/selectRobot一个是加#/selectRobot本地地址多了一个/ http://0.0.0.0:8081/#/selectRobothttp://www.baidu.com.index.html#/selectRobot这两个URL都带#说明它们都是Hash模式。它们的核心区别不在于模式而在于服务器上的文件路径结构。本地服务器把index.html放在了根路径/而你的打包文件xxxx.index.html本身就是一个放在服务器根路径下的、名字很长的HTML文件。当你访问http://0.0.0.0:8081时本地服务器默认会返回根目录下的index.html文件。等价于访问http://0.0.0.0:8081/index.html#/selectRobot只是省略了index.html。特性Hash 模式History 模式URL 示例http://www.baidu.com.index.html#/selectRobothttp://0.0.0.0:8081/selectRobot符号使用#(hash)使用/(真实路径)原理利用#后的内容变化不会触发浏览器刷新或向服务器发送请求的特性。利用 HTML5 History API (pushState,replaceState) 来改变URL不会真正刷新页面。服务器要求无特殊要求。因为#后的内容服务器会忽略始终返回index.html。需要特殊配置。因为像/selectRobot这样的路径是一个真实的URL如果服务器没有正确配置访问它会返回404错误。美观度不美观URL中带#。美观看起来和普通的URL一样。部署难度简单非常适合静态资源服务器。稍复杂需要在服务器端做回退处理Fallback即所有未匹配到静态资源的请求都返回index.html。HTTP协议和Web服务器发送请求自动补全使空路径默认指向根路径如url末尾带/不会影响两种模式最简单的路由区别举例假设我们有一个项目有两个页面首页/和一个关于页/about。1. Hash 模式 (默认模式)首页:http://yourdomain.com/your-project/#/关于页:http://yourdomain.com/your-project/#/about浏览器实际请求的始终是http://yourdomain.com/your-project/index.html这个文件。#/about的变化由前端的 Vue Router 自己处理用于决定具体显示哪个组件。2. History 模式首页:http://yourdomain.com/your-project/关于页:http://yourdomain.com/your-project/about浏览器在首次加载时会请求http://yourdomain.com/your-project/index.html。当点击跳转到“关于页”时Vue Router 会通过 History API无刷新地将地址栏变成/about。但是如果你直接在这个地址栏输入http://yourdomain.com/your-project/about并回车浏览器会向服务器发起一个真实的 HTTP 请求请求/about这个路径下的资源。如果服务器如 Nginx, Apache没有配置好找不到这个路径对应的文件就会返回404 Not Found。路由切换1.配置路由表的字段path、element/component、childrenpath指定路径elementReact/component(Vue)对应组件children嵌套路由export default dataConfirmChildrenVue命名路由nameparams 的自动编码/解码{ path: /about/foo/:id, name: foo, component: Foo } { name: foo, params: {id: 123} }命名视图同级展示路由元信息meta任意附加信息如授权meta: { auth: false } this.$route.meta.auth import { useLocation, matchRoutes, Navigate } from react-router-dom import { routes } from ../../router; export default function BeforeEach(props) { const location useLocation(); const matchs matchRoutes(routes, location) const meta matchs[matchs.length-1].route.meta if(meta.auth){ return Navigate to/login / } else{ return ( div{ props.children }/div ) } }2.根据路由表生成路由对象createBrowserRouterimport { createBrowserRouter, createHashRouter } from react-router-dom //路由表 export const routes []; //路由对象 const router createBrowserRouter(routes); export default router;3.在主入口/src/router/index.js结合路由配置文件RouterProvider router{router}import { RouterProvider } from react-router-dom import router from ./router; const root ReactDOM.createRoot(document.getElementById(root)); root.render( React.StrictMode RouterProvider router{router}/RouterProvider /React.StrictMode );传参行为Query 参数Params 参数 (未定义在 path)表现形式留在 URL 中 (?urlxxx)存在内存中URL 不可见按 F5 刷新页面保留从地址栏重新解析丢失内存被清空组件不重载时的复用需依靠watch或:key驱动更新需依靠watch驱动更新Vue Router 4 (Vue 3 取消 无path匹配的 params 传参 特性是因为这种“纯内存参数”破坏了 Web 网页的核心原则——状态可预测性。正常的 URL 应该做到“所见即所得”你把当前地址栏的链接复制发给你的同事他打开后看到的界面应该和你一模一样。但如果允许隐式传递内存 params你同事打开这个链接时因为内存里没有数据页面就会白屏或报错。这种“刷新就崩、分享就死”的特性成了很多前端新手的 Bug 重灾区。参数变更-组件更新当 URL 里的 query 从?id1变成?id2时组件实例是同一个。如果你希望组件对这个变化做出响应标准姿势有两个姿势 A全量刷新最省心在router-view上绑定一个唯一的 key逼迫 Vue 只要路由变了就重新实例化组件。router-view :key$route.fullPath/router-view姿势 B定点监听性能好使用watch捕获特定参数的变化手动触发局部数据的重新请求。watch: { $route.query.id(newId, oldId) { // 只要 id 变了我就重新拉取接口不用整页销毁 this.fetchData(newId); } }query显式携带辅助信息path: /user/,$route.queryquery显式 /workbenchConfiguration/upload?wpReleId59import { useSearchParams } from react-router-dom const [searchParams, setSearchParams] useSearchParams() console.log( searchParams.get(age) ); const handleClick () { setSearchParams({ age: 22 }) } // 1. 传参跳转 ➡️ 生成的 URL 为: /user?urlhttps://abc.comtype1 this.$router.push({ name: User, query: { url: https://abc.com, type: 1 } }); // 2. 显式清除某个参数 ➡️ 地址栏会抹除 url只留下 /user?type1 this.$router.push({ query: { ...this.$route.query, url: undefined } });params显式/隐式刷新页面会消失差异化界面$route.params显式path: /user/:id,隐式path: /user/,query显式 /workbenchConfiguration/upload?wpReleId59params显式 /workbenchConfiguration/upload/59如果不想让数据挂在 URL 上又想刷新不丢失的敏感参数最标准的做法是配合sessionStorage或状态管理const routes [ { // :id 就是占位符如果加个 ? (如 :id?) 代表这个参数是可选的 path: /user/:id, name: UserDetails, component: UserDetailsComponent } ] // 1. 传参跳转 ➡️ 生成的 URL 为: /user/9527 (数据绑在 URL 中刷新绝不丢) // 【注意】使用 params 必须搭配 name 属性跳转不能直接写 path this.$router.push({ name: UserDetails, params: { id: 9527 } }); // 2. 目标组件中接收 console.log(this.$route.params.id); // 输出: 9527分隔URL和参数http://example.com/page?param1value1param2value2#section1#锚点Anchor标识文档中的特定位置或元素由浏览器处理,不发送到服务器指示浏览器滚动到具有idsection1的元素处location属性值window的全局对象表示当前页面http://www.example.com/path/index.htmlwindow.location.href获取/设置 urlwindow.location.orgin协议、主机名和端口号部分//https://www.example.com:8080/page.html // :// : //https%3A%2F%2Fwww.example.com%3A8080。 encodeURIComponent(window.location.origin) //encodeURIComponent用于将字符串中的特殊字符(空格、、、 、?)转换为编码形式确保URL中不包含任何无效字符 //查询参数时 或者 动态参数时 需要encodeURIComponent const url https://example.com/api?param encodeURIComponent(queryParam); window.location.href https://www.example.com/path/to/resource.html/domain${location.host}req${encodeURIComponent(location.pathname)}protocolhttps${location.hash}window.location.protocol: 协议httpwindow.location.host主机端口host:8080/IP地址127.123.32.1唯一/域名www.example.com助记window.location.hostname主机hostwindow.location.port端口8080window.location.pathname: 资源路径path/index.html资源index.htmlwindow.location.hash:window.location.search: 搜索var searchParams new URLSearchParams(window.location.search); console.log(searchParams.get(name)); // 输出 JohnreactuseLocation()import { useLocation } from react-router-domhash哈希值key唯一标识pathname路径searchquery值(需要把字符串解析成对象。)state隐式数据路由分类Link标签和a标签区别Link to/首页/Linka原生超链接标签触发页面刷新不适合单页应用 (SPA)带样式的声明式路由NavLink动态路由(不同parmas访问同一个组件)import { Outlet, Link } from react-router-dom export default function About() { return ( div Link to/about/foo/123foo 123/Link | Link to/about/foo/456foo 456/Link /div ) } //... { path: foo/:id, element: Foo / } //... import { useParams } from react-router-dom export default function Foo() { const params useParams() return ( divFoo, { params.id }/div ) } //Vue与React唯一不同 this.$route.params.id编程式路由(非link模式)navigate useNavigate()//vue this.$router.push({ path: /about/foo/${id} }) //react import {useNavigate } from react-router-dom const navigate useNavigate() const handleClick () { navigate(/about/foo/123) }默认路由没有匹配成功 、重定向路由redirect/Navigate 、404*/errorElementVueimport VueRouter from vue-router import Home from /views/Home.vue const About{template:divAbout/div} const routes: Arrayany [ { path: /, redirect: /workbenchConfiguration }, { path: /404, meta: { title: 404 }, component: () import(/views/404.vue) }, { path: *, redirect: /404 } ] const router new VueRouter({ routes })Reactimport { createBrowserRouter, createHashRouter } from react-router-dom //路由表 export const routes [ // 默认路由 { index: true, //重定向 element: Navigate to/about/foo/123 /, //自带errorElement errorElement: div404/div, }, //*局部404 { path: *, element: div404/div } ]; //路由对象 const router createBrowserRouter(routes); export default router;loader回调函数(路由前触发)默认同步配合redirect做权限拦截。{ path: bar, element: Bar /, //asyncawait异步Promise用于表示一个异步操作的最终完成或失败及其结果值。 loader: async() { let ret await new Promise((resolve){ setTimeout((){ resolve({errcode: 0}) }, 2000) }) return ret; } }useLoaderData()获取loader函数返回的数据import { useLoaderData } from react-router-dom export default function Bar() { const data useLoaderData() console.log(data) return ( divBar/div )loader函数中是没有办法使用Navigate组件进行重定向操作的{ path: bar, element: Bar /, loader: async() { let ret await new Promise((resolve){ setTimeout((){ resolve({errcode: Math.random() 0.5 ? 0 : -1}) }, 2000) }) if(ret.errcode 0){ return ret; } else{ return redirect(/login) } } }主入口//index.js import { RouterProvider } from react-router-dom import router from ./router; const root ReactDOM.createRoot(document.getElementById(root)); root.render( React.StrictMode RouterProvider router{router}/RouterProvider /React.StrictMode );守卫权限、路由拦截权限/拦截一个页面很多路由SPA登录后都能访问有一个页面需要额外认证导航解析流程导航被触发。在失活的组件里调用beforeRouteLeave守卫。调用全局的beforeEach守卫。在重用的组件里调用beforeRouteUpdate守卫(2.2)。在路由配置里调用beforeEnter解析异步路由组件。在被激活的组件里调用beforeRouteEnter。调用全局的beforeResolve守卫(2.5)。导航被确认。调用全局的afterEach钩子。触发 DOM 更新。调用beforeRouteEnter守卫中传给next的回调函数创建好的组件实例会作为回调函数的参数传入。全局Vue//to router.beforeEach((to, from, next){ if(to.meta.auth){ next(/); } else{ next(); } }) //vuets router.beforeEach((to: any, from: any, next: any) { const metaTitlt (to.meta to.meta.title) || document.title ${metaTitlt} - 默认模版 //是否从根路径而来当前路由的来源路径和即将进入的路由的路径是否相同 if (from.path ! / from.matched[0].path ! to.matched[0].path) { message.destroy() } next() })React/src/components/BeforeEach.jsximport React from react import { Navigate } from react-router-dom import { routes } from ../../router; export default function BeforeEach(props) { if(true){ return Navigate to/login / } else{ return ( div{ props.children }/div ) } }export const routes [ { path: /, element: BeforeEachApp //BeforeEach//包裹根组件APP } ]局部路由相比组件路由更推荐局部更好地逻辑分块const routes [ { name: bar, component: Bar, beforeEnter(to, from, next){ if(to.meta.auth){ next(/); } else{ next(); } } } ];组件路由script export default { name: FooView, beforeRouteEnter(to, from, next){ if(to.meta.auth){ next(/); } else{ next(); } } } /script浏览器输入URL展示路由对应组件没有占位的话默认整个页面Vue占位router-view\router-linktemplate div router-link to/首页/router-link | router-link to/about关于/router-link router-view/router-view /div /templateReact占位Outlet\Linkimport React from react; import { Outlet, Link } from react-router-dom function App() { return ( div classNameApp h2hello react/h2 Link to/首页/Link | Link to/about关于/Link Outlet / /div ); } export default App;带样式的声明式路由NavLink路由跳转push保留历史记录可返回将新的路由添加到浏览器的历史记录中这样用户就可以通过浏览器的后退按钮回到之前的路由。this.$router.push(/about)默认复用缓存的组件触发 activated只有当你使用了 keep-alive 标签包裹了 router-view并且在 SPA单页应用内部切走又切回来时组件没有被销毁才会触发 activated()激活和 deactivated()停用。配置{ path: /main/meet, name: meet, component: (resolve: any) require([/views/serviceCenter/index.vue], resolve), // iframeComponent: meet },keep-alivemain/index.vuekeep-alive includetodoTask,serviceCenter router-view/router-view /keep-alivekeep-alive include是根据组件的name选项来匹配的而不是路由的name。动态加载的组件注册name如果组件是动态加载的如() import(/views/meet)确保导出的组件有name属性export default { name: serviceCenter,activated配合恢复滚动位置等上次状态activated() { this.focusInput(delay); this.restoreScrollPosition(); }, beforeRouteLeave(to, from, next) { next(); this.scrollTop { history: document.querySelector(.chat_history_box).scrollTop || 0, panel: document.querySelector(.drawer_body).scrollTop || 0, }; },replace不保留适合登陆页面登录成功后可能会用replace方法替换当前路由以防止用户通过后退按钮回到登录页面。this.$router.replace(/contact)可能不会触发 activated特别是如果key变化或组件未正确缓存。导航重复修改原型push、replace方法修改 VueRouter 的原型方法push和replace用来捕获导航重复错误并进行处理而不会在控制台中抛出错误从而避免了不必要的错误提示和潜在的问题。import Vue from vue; import VueRouter from vue-router; Vue.use(VueRouter); const originalPush VueRouter.prototype.push; VueRouter.prototype.push function push(location) { return originalPush.call(this, location).catch(err { if (err.name ! NavigationDuplicated) { throw err; } }); }; const originalReplace VueRouter.prototype.replace; VueRouter.prototype.replace function replace(location) { return originalReplace.call(this, location).catch(err { if (err.name ! NavigationDuplicated) { throw err; } }); }; const router new VueRouter({ // 路由配置... }); export default router;物理导航vs Router维度浏览器按钮键盘/移动端手势前进后退Router 前端路由操作本质浏览器行为应用内行为控制权由浏览器历史记录栈控制由前端路由库Vue Router/React Router控制URL 变化URL 改变并向服务端发起请求若未处理仅改变URL 的路径部分无服务端请求页面动作完整页面刷新默认行为会重建DOMSPA 组件切换仅更新部分视图无刷新数据状态页面状态丢失JS 内存被清空页面状态保留在 SPA 内性能差网络请求、重新解析渲染好本地JS执行瞬时切换开发者干预通过popstate事件监听可阻止默认行为完全可控可添加导航守卫、动画等底层API操作的是window.history对象。核心事件popstate事件。当历史记录条目改变时用户执行前进/后退会触发此事件。// 监听浏览器前进后退 window.addEventListener(popstate, (event) { console.log(位置变了:, window.location.pathname); // 可以在这里同步前端路由的状态但无法阻止手势本身 });触发条件在应用内点击router-link或调用router.push()/router.go()。底层原理通过history.pushState()/history.replaceState()API修改历史栈但不触发页面刷新。// Vue Router 示例 this.$router.push(/next-page); // 添加一条记录并跳转 this.$router.go(-1); // 向前移动一条记录等同于浏览器后退 this.$router.go(1); // 向后移动一条记录等同于浏览器前进应用导航守卫处理权限或数据保存// Vue Router 示例在离开页面前保存数据 beforeRouteLeave(to, from, next) { if (this.formChanged) { if (confirm(内容未保存确定离开)) { next(); } else { next(false); // 取消导航 } } else { next(); } }