在 Web 前端开发中,路由是非常重要的一环,但是路由到底是什么呢?
从路由的用途上讲
路由是指随着浏览器地址栏的变化,展示给用户不同的页面。
从路由的实现原理上讲
路由是URL到函数的映射。它将 URL 和应用程序的不同部分映射到特定的处理程序或控制器上。
路由本身也经历了不同的发展阶段:
服务端路由
服务端路由也称后端路由。
服务器根据用户访问的 URL 路径返回不同的响应结果。当我们在一个传统的服务端渲染的 web 应用中点击一个链接时,浏览器会从服务端获得全新的 HTML,然后重新加载整个页面。
对于最简单的静态资源服务器,可以认为所有URL的映射函数就是一个文件读取操作。对于动态资源,映射函数可能是一个数据库读取操作,也可能是进行一些数据处理等等。
- 好处
安全性好、利于 SEO(Search Engine Optimization,搜索引擎优化。是指为了增加网页在搜索引擎自然搜索结果中的收录数量以及提升排序位置而做的优化行为)
- 缺点
加大服务器的压力,不利于用户体验,代码冗余
正式服务端路由得缺点,让客户端路由开始展露头角。
正式服务端路由得缺点,让客户端路由开始展露头角。
2. 客户端路由
客户端路由也称前端路由。
当用户通过客户端访问不同的路径时,路由的映射函数利用诸如 History API 或是 hashchange 事件这样的浏览器 API 来管理应用当前应该渲染的视图,其根本就是操作 DOM 的显示和隐藏。
基于 hash 的实现
早期的前端路由的实现就是基于location.hash来实现的。实现原理也很简单就是:监听#后面的内容来发起Ajax请求来进行局部更新,而不需要刷新整个页面。
location.hash的值就是URL中#后面的内容。
例如:https://www.happy.com#me中的 location.hash = ‘#me’
hash也存在下面几个特性:
1.URL中的hash值只是客户端的一种状态,也就是说当向服务器发出请求时,hash部分不会被发送。
2.hash值的改变,都会在浏览器的访问历史中增加一个记录,因此我们能通过浏览器的回退,前进按钮控制hash 的切换。
3.我们可以使用hashchange事件来监听URL的变化
触发hash变化的方式也有两种:
1.通过a标签,并设置href属性,当用户点击这个标签后,URL就会发生变化,也就会触发hashchange事件了
例如:hahha
2.直接使用js来对location.hash进行赋值,从而改变URL 触发hashchange事件
例如: location.hash = '#hahha'
3.浏览器前进后退改变 URL,触发hashchange事件
基于 history API 的实现
介绍HTML5 history API
HTML5 history API只包括2个方法:history.pushState()
和history.replaceState()
,以及1个事件:window.onpopstate
。
1.切换历史状态
包括 back() 、forword() 、go(n) 三个方法,分别对应浏览器的前进,后退,跳转操作
history.go(-2) //后退两次 + 刷新
history.go(2) //前进两次
history.back() //后退 (不刷新)
history.forword() //前进
back() 方法可加载历史列表中的前一个 URL(如果存在)。调用该方法的效果等价于点击后退按钮或调用 history.go(-1)
forward() 方法可加载历史列表中的下一个 URL。调用该方法的效果等价于点击前进按钮或调用 history.go(1)
2.修改历史状态
包括 pushState() 、replaceState() 两个方法,都接收三个参数:stateObj 、title、url。它们可以在不刷新页面的情况下,操作浏览器的历史记录。
pushState() 新增一个历史记录
replaceState() 替换当前的历史记录
popstate() 事件。用于监听历史记录的变化
参数:
状态对象(state object):一个JavaScript对象,与用 pushState() 方法创建的新历史记录条目关联。无论何时用户导航到新创建的状态,会触发popstate 事件,并能在事件中使用该对象
标题(title):一般浏览器会忽略,传 null 即可
地址(URL):需要新增的历史记录地址,浏览器不会去直接加载改地址,但后面也可能会去尝试加载该地址。此外需要注意的是,传入的URL与当前URL应该是同源的。
window.onpopstate = function(event) {
alert("location: " + document.location + ", state: " + JSON.stringify(event.state));
};
history.pushState({page: 1}, "title 1", "?page=1");
history.pushState({page: 2}, "title 2", "?page=2");
history.replaceState({page: 3}, "title 3", "?page=3");
history.back(); // 弹出 "location: http://example.com/example.html?page=1, state: {"page":1}"
history.back(); // 弹出 "location: http://example.com/example.html, state: null
history.go(2); // 弹出 "location: http://example.com/example.html?page=3, state: {"page":3}
**总结:**无论是客户端路由还是服务端路由,都有一定的应用场景和适用范围。对于需要提供优秀用户体验和动态交互的 Web 应用,客户端路由更为适用;而对于需要提供更好的SEO优化和网站性能的网站,服务端路由更为适用。但之后的出现了nuxt.js和next.js,很大程度上弥补了传统的客户端路由不利于SEO优化的问题。
Vue 很适合用来构建单页面应用。对于大多数此类应用,都推荐使用官方支持的Vue Router;在单页面应用(Single-page application)中,客户端的 JavaScript 可以拦截页面的跳转请求,动态获取新的数据,然后在无需重新加载的情况下更新当前页面。这样通常可以带来更顺滑的用户体验,因为这类场景下用户通常会在很长的一段时间中做出多次交互。这类的单页面应用中,路由的更新是在客户端执行的。
Vue Router 是 Vue 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举。功能包括:
以下我们来介绍Vue Router的基本使用。
构建前端项目
npm init vue@latest
//或者
npm init vite@latest
对于vue3,我们推荐使用vue-router 4.x及以上版本。node环境安装如下:
# yarn 方式
yarn add vue-router@4
# npm 方式
npm install vue-router@4
安装成功后的 package.json
在我们工程项目中,路由文件通常需要单独管理,以便于后续的使用以及维护。再此基础下,我们引入分为两步:
在与main.ts文件的同级目录下创建router文件夹,并添加index.ts文件(使用ts,若使用js也同理)。
文件内容如下:
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import Home from "../views/Home.vue";
// 项目其他页面路由(推荐使用)
import Practice from "../router/practiceFolder/practice";
// vue项目自带路由
const routes: Array = [
{
path: "/",
name: "Home",
component: Home
},
{
path: "/about",
name: "About",
component: () =>
import(/* webpackChunkName: "about" */ "../views/About.vue")
}
];
const routers = [...routes, ...Practice];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes: [...routes, ...Practice]
});
export default router;
其中,使用RouteRecordRaw
声明的对象会被当做路由对象,放入到路由对象池里。
createRouter
创建路由;
createWebHistory
代表路由使用 HTML5 模式,也是推荐使用的模式;
# src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router' // 默认查找当前路径下的 index.js,同 './router/index'
const app = createApp(App)
app.use(router)
app.mount('#app')
组件中使用路由,涉及到**“路由跳转”和“路由出口”**两个概念:
自定义组件,用于创建链接。可以使 Vue Router 在不重新加载页面的情况下更改 URL,处理 URL 的生成以及编码。
路由出口,用于显示与 url 对应的组件(路由映射组件),可以放到任何位置。
Hello App!
Go to Home
Go to About
在App.vue中使用
组件来渲染要显示的组件,在Tabbar组件中使用
组件生成链接
createRouter(options) 创建一个可以被 Vue 应用使用的 Router 实例
createRouter({
history: createWebHistory(),
routes: [
{ path: '', name: '', meta: { }, component: CompA, ...}
// 其它路由组件配置
]
// 其它选项配置...
})
路由器使用的历史记录模式,大多数应该使用 createWebHistory ,但这需要正确的配置服务器。
如果不想配置服务器,可以使用 createWebHashHistory 来实现基于 hash 的历史记录,但这种方式不会被搜索引擎处理,SEO 效果较差
createMemoryHistory(base?) 创建一个基于内存的历史,主要用于处理服务端渲染。它从一个不存在的特殊位置开始,可通过 router.push 或 router.replace 将该位置替换成起始位置。
// 基于 https://example.com/folder
createWebHashHistory() // 给出一个 `https://example.com/folder#` 的 URL
createWebHashHistory('/folder/') // 给出一个 `https://example.com/folder/#` 的 URL
注:在组件卸载时,所有路由守卫会被移除。
router 操作函数
push() 程序式地通过将一条记录加入到历史栈中来导航到一个新的 URL
resolve() 返回一个路由地址的规范化版本。同时包含一个包含任何现有 base 的 href 属性
replace() 程序式地通过替换历史栈中的当前记录来导航到一个新的 URL
addRoute(parentName, route) 添加一个新的路由记录,将其作为一个已有路由的子路由。
我们在使用Vue Router 中的createRouter创建router对象时,其为我们提供了很多配置项,带完整配置项的示例代码如下:
const router = createRouter({
history: createWebHashHistory(),
routes: [],
scrollBehavior: () => ({ top: 0, left: 0 }),
linkActiveClass: 'active',
linkExactActiveClass: 'exact-active',
parseQuery: null,
stringifyQuery: null,
fallback: true
})
上面代码中各个配置项的含义如下:
上面的配置项中,我们一般只需要配置history和routes两个选项就可以了,其它选项了解即可
在 Vue Router 中,路由规则的配置是通过 routes 属性来实现的。routes 属性中常用的配置如下:
通过Vue Router,我们可以通过router-link
组件的to方法和使用router.push
函数以编程方式两种方法导航到路由。
router-link
组件使用router-link
组件实现路由跳转,我们只需要将菜单按钮使用router-link
组件包裹,并在上面使用to方法即可进行跳转,示例代码如下:
Home
List
About
router.push
函数使用router.push
函数以编程方式实现路由跳转,我们只需要在普通按钮上绑定click事件,并在事件中调用router.push()
方法即可实现跳转,示例代码如下:
Home
List
About
使用 router.push 方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,会回到之前的 URL。
事实上,当我们点击
router.push()方法中的参数可以是一个字符串路径,或者一个描述地址的对象。
// 字符串路径
router.push('/users/eduardo')
// 带有路径的对象
router.push({ path: '/users/eduardo' })
// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })
// 带查询参数,结果是 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })
// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' })
在Vue Router中,可以通过以下方式进行路由传参和获取参数
const routes= [
{
path: '/detail/:id',
name: 'Detail',
component: Detail
}
]
在路由路径中使用冒号表示参数,参数值会被放入route.params
对象中。我们可以通过调用route.params
获取参数,如访问地址为/detail/123,则我们可以通过route.params.id获取值为"123"。
2 通过query参数传递参数:在路由跳转时使用query参数,例如:
// 在组件中跳转
router.push({
path: '/detail',
query: { id: 123 }
})
// 在模板中跳转
Detail
在路由中使用query参数时,参数值会被放入route.query
对象中。我们可以通过route.query获取参数。例如,访问地址为/detail?id=123,则我们可以通过route.query.id获取值为"123"。
3 在路由配置中通过props选项传递参数。例如:
const routes= [
{
path: '/detail/:id',
name: 'Detail',
component: Detail,
props: { id: 123 }
}
]
在组件中可以直接使用props接收参数
4 在路由配置中通过meta选项传递参数。例如:
const routes= [
{
path: '/detail/:id',
name: 'Detail',
component: Detail,
meta: { id: 123 }
}
]
在组件中可以通过route.meta获取参数,
动态路由是指将一个路由的一部分作为参数来构建的路由。例如,如果我们要为每个用户创建一个单独的页面,我们可以使用动态路由,创建一个路径为/users/:userId的路由,其中:userId是一个参数。
动态路由在定义路由时使用冒号(:)来表示参数。定义动态路由需要使用path方式定义。例如,要定义一个动态路由,我们可以这样写:
{
path: '/users/:userId',
name: 'user',
component: User
}
在上面的代码中,路径中的:userId
表示一个参数,它可以从路由对象的params
属性中获取。在组件中可以这样读取userId
:
console.log(route.params.userId)
在使用动态路由时,Vue Router还支持使用可选的参数和正则表达式来定义路由。例如,可以这样定义一个包含可选参数的动态路由:
{
path: '/users/:userId/:postId?',
name: 'post',
component: Post
}
在上面的代码中,路径中的postId
参数是可选的,我们在它后面加一个问号代表可选参数。现在,如果路径是/users/123
,那么postId
将是undefined
;如果路径是/users/123/456
,那么postId
将是456
。
嵌套路由允许我们在一个父级路由下嵌套多个子路由,从而形成更加复杂的页面结构。
要定义嵌套路由,我们可以在父级路由的routes数组中定义一个子路由对象数组,每个子路由对象都包含一个path和一个component属性,表示当前子路由的访问路径和对应的组件。同时,我们还可以在子路由对象中定义子路由的子路由,从而形成嵌套的路由结构。
我们使用配置项children表示路由的嵌套关系,如下示例代码:
const routes = [
{
path: '/',
component: Home,
children: [
{
path: 'about',
component: About
},
{
path: 'contact',
component: Contact
}
]
}
]
在上面的代码中,我们定义了一个名为Home
的组件作为父级路由的组件,并在children
数组中定义了两个子路由:About
和Contact
。这样,当用户访问/about
或/contact
时,Vue Router 就会渲染对应的子组件,并将其嵌套在Home
组件内。
命名路由
命名路由可以为路由设置一个名称,以便在代码中进行引用和跳转。使用命名路由可以让代码更加清晰易懂,尤其是在需要跳转到具有动态参数的路由时。
要定义命名路由,我们可以在路由对象中使用name
属性来指定路由的名称,例如:
const routes = [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: About
},
{
path: '/user/:id',
name: 'user',
component: User
}
]
在上面的代码中,我们为三个路由分别指定了名称:home
、about
和user
。然后,在代码中,我们可以通过这些名称来生成对应的路由链接或路由跳转,例如:
Home
About
User 123
router.push({name: 'home'})
router.push({name: 'user', params: {id: '456'}})
在上面的代码中,我们分别使用了
组件和router.push()
方法来跳转到具有命名路由的路由。其中,使用params
属性可以动态指定路由中的参数。
命名路由在需要动态传递参数的情况下使用非常方便。
一文详解:Vue3中使用Vue Router_router vue3_九仞山的博客-CSDN博客
路由守卫是一种函数,在路由的各个阶段被调用,可以用于拦截路由的访问或对路由进行一些操作。我们可以使用路由守卫来控制路由的跳转和访问权限。
在路由守卫中,我们通常会用到三个参数:to
、from
和next
。
几种使用next
函数的情况
next()
: 表示继续执行下一个路由守卫。
next(false)
: 表示取消当前的路由跳转。
next('/path')
: 表示跳转到指定的路由路径。
next(error)
: 表示在路由跳转过程中发生了错误,例如权限不足等。
需要注意的是,在使用路由守卫时,我们需要显式地调用next
函数来控制路由的跳转和功能,否则路由不会继续向下执行。在不同的守卫中,next
函数的行为和功能也会有所不同,需要根据具体的场景进行调用。
Vue Router中的路由守卫分为全局路由守卫和路由独享守卫:
全局路由守卫是在整个应用中都生效的守卫,可以用于拦截所有的路由操作。在Vue Router@4中,全局守卫有三个:beforeEach
、beforeResolve
和afterEach
。
一个验证用户是否登录的路由守卫示例代码如下
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
else next()
})
上面代码中,如果用户没有登录,则页面跳转到Login页面,如果已经登录,则执行next()跳转
路由独享守卫仅对当前路由生效,可以用于限制或增强某个路由的访问权限。在Vue Router@4中,路由独享守卫有两个:beforeEnter
和leaveGuard
。
beforeEnter
: 在进入当前路由之前执行,可以用于增强当前路由的访问权限或进行相关操作。
leaveGuard
: 在离开当前路由之前执行,可以用于给用户提示或进行相关操作。
使用路由守卫时,我们可以在createRouter
函数中进行注册,例如:
const routes=[
{
path: '/',
component: Home
},
{
path: '/about',
component: About,
beforeEnter: (to, from, next) => {
// 进行路由访问控制或相关操作
}
}
]
路由懒加载是一种将路由组件按需异步加载的方式,只有当路由对应的组件需要使用时,才会动态地加载该组件对应的代码。使用路由懒加载可以优化应用程序的性能
在Vue Router中使用路由懒加载,我们可以通过使用import()
和动态import()
两种方式来实现
const Home = () => import('./views/Home.vue')
const About = () => import('./views/About.vue')
const routes = [
{
path: '/',
component: Home
},
{
path: '/about',
component: About
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
const routes = [
{
path: '/',
component: () => import('./views/Home.vue')
},
{
path: '/about',
component: () => import('./views/About.vue')
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
useRoute()、useRouter()本质是两个函数,是用于获取路由相关信息。useRoute():用于返回当前路由信息对象用于接收路由参数。useRouter():用于返回当前路由实例,常用于实现路由跳转。使用方法先引入且需要调用两个函数才可以得到相关信息。
打印useRoute(),可以看到返回的是对象,里面包含matched、meta、name、params、query、path等路由参数信息。
fullPath:显示当前路由路径(包含子路由,如 value: "/acl/user");
matched:值为数组,其数组的长度是路由层级,数组内容包含二级路由和完整路由,如下图:
关于matched需要注意,在面包屑功能中,需要用到matched提供的数组信息进行渲染
meta:获取当前路由元信息;
name:当前路由名称;
params、query:当前路由参数;
path:当前路由完整路径;
useRouter()对象中,包含了很多路由跳转的方法:replace()、push()、back()、go()、addRoute()、hasRoute()、removeRoute()等常用的路由方法
路由器:Vue Router 提供了一个路由器,用于管理应用程序中的路由。Vue Router 实例化一个 Vue Router 对象,注册路由规则,并以它为中心连接其他组件。
路由:路由是分发到不同组件的 URL 地址。在 Vue Router 中,路由通常是由 path 规则和相应的组件定义的。当浏览器的 URL 匹配到路由的 path 后,相应的组件将会被加载到页面中。路由的信息可以从 route 对象中获取。
路由规则:路由规则是由 path、component、name、meta、props 等属性组成的。其中,path 表示 URL 的路径,component 表示要渲染的组件,name 表示路由名称,meta 表示路由的元数据,props 表示路由 props 数据。路由规则可以注册到 Vue Router 中。
导航守卫:导航守卫是在路由跳转时执行的钩子函数,用于控制路由的访问权限、处理路由跳转前后的逻辑等。在 Vue Router 中,对于选项式 API,常用的导航守卫有 beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave 等;对于使用组合 API 和 setup 函数来编写组件的,可以通过 onBeforeRouteUpdate 和 onBeforeRouteLeave 分别添加 update 和 leave 守卫。
一文详解:Vue3中使用Vue Router_router vue3_九仞山的博客-CSDN博客
【Vue3 生态】Vue Router 路由知识概览_vue3路由_MagnumHou的博客-CSDN博客
什么是路由?_chenwan8029的博客-CSDN博客
HTML5 history API,创造更好的浏览体验_清蓝哈哈的博客-CSDN博客
浅析 useRoute() 、useRouter()_useroute和userouter的区别_tomtomgogo的博客-CSDN博客