路由的定义
维护一个映射表 => 决定数据的流向
1.对URL和内容进行映射
2.监听URL的改变
路由发展的阶段
后端路由阶段
由服务器来渲染
做法:
1.每一个页面都有自己对应的URL
2.URL发送给服务器之后,服务器会通过正则对URL进行匹配,最后交给一个Controller处理
3.Controller经过处理完成后,生成HTML/数据,返回给前端
| 优点 | 缺点 |
|---|---|
| 根据请求不同的路径内容,服务器会直接渲染好整个页面,直接返回给客户端 | 前端开发页面困难:需要通过PHP/Flex/Java开发页面 |
| 浏览器直接展示,有利于SEO优化 => 不需要单独加载js和css | 难以维护:HTML代码/数据/逻辑高度耦合 |
前端路由阶段
Ajax出现后
- 特点
- 每次请求涉及到的静态资源(HTML+CSS+JS)都需要从静态服务器中获取
- 服务器返回静态资源后,前端对这些资源进行渲染
- 前端通过Ajax获取数据,后端只负责提供API => 前端专注于交互和可视化,后端专注于数据
单页面富应用
- SPA的最主要特点:在前后端分离的基础上加了一层前端路由
hash路由
hash => 锚点 / #
本质上是改变window.location的href属性
直接通过loaction.hash来改变href,页面不会刷新
| 优点 | 缺点 |
|---|---|
| 是兼容性更好,在老版IE中可运行 | 有一个#,显得不像一个真实的路径 |
history路由
HTML5新增,
下面6种模式改变URL都不会刷新页面
| replaceState | 替换原来的路径 - 不做压栈和出栈的操作 - 用于不希望用户可以回退操作的业务场景 |
|---|---|
| pushState | 使用新的路径 - 往栈中压入一个地址 |
| popState | 路径的回退 |
| go | 向前或者向后改变路径 - 参数:弹出(负数)/压入的个数 - 跳转到栈的某一个位置 |
| forward | 向前改变路径 |
| back | 向后改变路径-弹出栈中最顶部的地址 |
VueRouter
步骤:
- 创建路由组件
- 配置路由映射,组件和路径映射的关系
- 使用路由,通过
<router-link>和<router-view>
router-link:会被渲染成一个a标签<router-view>:会根据当前的路径,动态渲染出不同的组件。网页的其他内容,比如顶部导航栏,标题,底部的一些版权信息会和<router-view>处于同一层级- 在路由切换时,切换的是
<router-view>挂载的组件,其他内容不会发生改变
使用
1. router对象需要挂载到vue实例中
import Vue from 'vue'
import App from './App.vue'
import router from './router' // 导入路由映射表
import store from './store'
new Vue({
router, // router对象需要挂载到vue实例中
store,
render: (h) => h(App)
}).$mount('#app')
2. 配置路由映射表
需要使用Plugin插件:VueRouter
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // 需要传入plugin插件--VueRouter
2.1 创建路由对象
配置路由和组件之间的映射关系,注意还需要在vue实例中挂载
const router = new VueRouter({
routes, // 对象数组,单独封出来
mode: 'history' // 默认的是哈希模式,可以更改为历史路由
})
// 添加全局导航守卫
// 前置钩子
router.beforeEach((to, from, next) => {
// 从from跳转到to
// doSomething
// 判断用户是否登录
const isLogin = localStorage.getItem('user') ? true : false;
if (to.path == '/login') {
// 如果已经登录,重定向到首页
isLogin ? next('/') : next();
} else {
// 如果未登录,重定向到登录页
isLogin ? next() : next('/login');
}
next() // 必须调用,不调用的话路由不会跳转
})
// 后置钩子(hook)
router.afterEach((to, from) => {
// 可用于统计页面访问次数。
console.log(to, from, 'afterEach')
})
export default router
导航守卫
监听跳转的过程,用于做相应的操作
| 全局守卫 | 路由独享守卫 | ||
|---|---|---|---|
| beforeEach(to, from, next) | 在路由切换前执行,可以用于判断用户是否有权限访问该路由,如果有权限则调用 next(),否则调用 next(false) 中止路由切换,或者调用 next('/') 或 next({ path: '/' }) 重定向到其他页面。 | beforeEnter(to, from, next) | 在路由被访问前执行,作用和 beforeEach 类似,但是只对当前路由生效 |
| afterEach(to, from) | 在路由切换后执行,可以用于处理一些全局的逻辑,如发送日志、统计页面访问次数等等 | afterEach(to, from) | 在路由切换后执行,作用和全局守卫的 afterEach 相同,但是只对当前路由生效。 |
| beforeResolve(to, from, next) | 在路由组件解析完毕后执行,可以用于处理异步数据等等,确保在渲染组件之前数据已经准备好。 |
| 入参 | |
|---|---|
| to | 即将进入的目标路由对象。 |
| from | 当前导航正要离开的路由对象。 |
| next | 调用该函数才能进入下一个钩子。 如果调用 next(),则进入下一个钩子; 如果调用 next(false),则中断当前的导航; 如果调用 next('/') 或 next({ path: '/' }),则重定向到其他页面。 |
- routes 对象数组的配置
const routes = [
{
path: '',
redirect: '/home'
},
{
path: '/home',
name: 'home',
component: Home, // 使用路由懒加载
meta: {
// 源数据
title: '首页'
},
children: [
{
path: 'news', // 注意:子路由不需要加分隔符 /
component: HomeNews
},
{
path: 'message',
component: HomeMessage
}
]
},
{
path: '/about',
name: 'about',
// component: AboutView
component: About,
meta: {
// 源数据
title: '关于'
},
// 添加路由独享守卫
beforeEach(to, from, next) {
// doSomething
next()
},
beforeEnter: (to, from, next) => {
// 判断用户是否已经登录
const isLogin = localStorage.getItem('user') ? true : false;
if (isLogin) {
next();
} else {
next('/home'); // 重定向
}
}
},
{
path: '/user/:userId', // 传参
name: 'user',
component: UserView
},
]
- 路由懒加载
原理:import的组件会被自动分包
// 统一一起管理动态组件
const Home = () => import('../components/Home.vue')
const About = () => import('../components/About.vue')
const HomeNews = () => import('../components/HomeNews')
const HomeMessage = () => import('../components/HomeMessage')
3. 假如在App.vue中挂载路由视图
import router from './router';
<template>
<div id="app">
<!-- router-link 是已经注册过的全局组件 -->
<router-link to="/home">Home</router-link> |
<router-link to="/about">About</router-link> |
<!--
动态路由:path 和 Component 是匹配关系;用于传递数据的一种
还需要在路由表中配置
template 中写对象,需要对 key 做 v-bind 绑定 => :to="{}"
有两种写法:
-->
<router-link
:to="'/user/' + userId"
>
User
</router-link> |
<router-link
:to="{ path: '/profile', query: { name: 'hakwan', age: '25' } }"
>
Profile
</router-link>
<!-- 使用其他方式跳转路由 -->
<button @click="profileClikHandle">用户档案</button>
<!--
router-view 是路由内容显示出来的位置的占位符
exclude: ProfileView和UserView不需要被缓存
-->
<keep-alive exclude="ProfileView,UserView">
<router-view />
</keep-alive>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
userId: 'hakwan'
}
},
methods: {
profileClikHandle() {
/* 若直接跳转,不携带参数
this.$router.push('xxx') */
this.$router.push({
path: '/profile',
query: {
name: 'wahaha',
age: '????'
}
})
}
}
}
</script>
-
在跳转路由时如何传递参数?
- 使用动态路由
params 1. 配置路由格式: /router/:id 2. 传递的方式: 在 path 后面跟上对应的值 3. 传递后形成的路径: /router/123, /router/abc 4. 通过$route.param 对象取值- 使用$router.push
query 1. 配置路由格式: /router, --> 普通配置 2. 传递的方式:对象中使用 query 的 key 作为传递方式, 想要传递的信息封装成对象作为value 3. 传递后形成的路径: /router?id=123, /outer?id=abc 4. 通过$route.query.keyName 取值 -
router 的区别:
- router.push 方法
- this.$route => 处于活跃的路由对象,可以获取 name,path,query,params 等
ReactRouter
react-router: 6版本
在 Web 应用中进行路由管理,需要使用
react-router-dom包
路由模式的选择
| BrowserRouter | HashRouter |
|---|---|
使用 HTML5 的 history API 来实现路由管理。它使用正常的 URL 形式,例如 https://example.com/about,并在浏览器中使用 pushState 和 popState 事件来实现 URL 的变化 | 使用 URL 中的 hash(即 # 符号)来实现路由管理。它使用的 URL 形式为 https://example.com/#/about,其中 # 符号后面的部分被称为 hash。因为 hash 不会被发送到服务器,所以在使用 HashRouter 时,浏览器不会向服务器发送请求,这样可以减少服务器的负担。但是,使用 hash 作为 URL 的一部分可能会导致 SEO 不友好。 |
// 使用方式 => 包裹根组件即可
<BrowserRouter>
<App />
</BrowserRouter>
<HashRouter>
<App />
</HashRouter>
路由映射配置
Routes:用于包裹所有的Route,在其中匹配一个路由(Route5.x 使用的是 Switch组件)
Route: 用于路径的匹配
path 属性: 用于设置匹配到的路径
element 属性:设置匹配到的路径后,渲染的组件(Route5.x 使用的是 component 属性)
exact: 精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件(Router6.x 不再支持该属性)
// Routes的使用
<Routes>
<Route path='/' element={<Home />}/>
</Routes>
路由的配置和跳转
1.处理路由导航
| Link | NavLink |
|---|---|
| 用于在应用程序中导航到不同的路由 | 用于在应用程序中呈现主菜单或导航栏。 |
不会在链接上添加任何额外的类或样式,这使得它比 NavLink 更轻巧。 | 是 Link 组件的一个扩展,它具有额外的功能,例如在当前路由匹配时自动添加类名或样式等 |
| style:传入函数,函数接受一个对象,包含isActive属性 | |
| className:传入函数,函数接受一个对象,包含isActive属性 |
import { Link, NavLink } from 'react-router-dom';
<Link to="/">Home</Link>
<NavLink to="/about" className="active">
About
</NavLink>
<NavLink
to="/bbout"
style={({ isActive }) => ({ color: isActive ? "#719CD3" : "" })}
>
Bbout
</NavLink>
<NavLink to="/cbout" className="active">
Cbout
</NavLink>
- Link的其他属性
| 属性 | 用途 | 示例 |
|---|---|---|
| replace | 设置为 true 后,路由的导航将使用 history.replace 替换 history.push,从而使新路由替换当前路由,而不是在历史记录中添加一个新条目。 | replace => true |
| state | 可以将任何数据作为 state 属性传递给新路由。这些数据可以在目标组件中使用 location.state 访问。 | state={{ Object }} |
| target | 指定目标页面的打开方式。默认是在当前窗口打开,但可以设置为 _blank(在新窗口打开)或其他值。 | target="_blank" |
Navigite导航
用于路由的重定向,当这个组件出现,就会执行跳转到对应的to路径中
// 当匹配到 / 时,跳转到登录页
<Route path="/" element={<Navigate to="/login"}/>
404页的配置
- 开发一个Not Found组件页面
- 配置对应的Route,并且设置path为“*”
<Route path="*" element={<NotFound />} />
路由基本结构配置
{/* 配置映射关系 path => components */}
<Routes>
{/* 使用navigate组件进行重定向 */}
<Route path="/" element={<Navigate to="/home" />} />
<Route path="/home" element={<Home />}>
{/* 当路径为home时,重定向到推荐列表 */}
<Route path="/home" element={<Navigate to="/home/recommend" />} />
<Route path="/home/recommend" element={<Recommend />} />
<Route path="/home/ranking" element={<Ranking />} />
<Route path="/home/songmenu" element={<SongMenu />} />
</Route>
<Route path="/about" element={<About />} />
<Route path="/login" element={<Login />} />
<Route path="/category" element={<Category />} />
<Route path="/order" element={<Order />} />
<Route path="/detail/:id" element={<Detail />} />
<Route path="/user" element={<User />} />
{/* 当全部路径都匹配不到时, 匹配notFound组件 */}
<Route path="*" element={<NotFound />} />
</Routes>
手动路由跳转的实现
路由跳转的方式:
- 通过
Link或者NavLink进行跳转 - 通过
Navigate组件的方式进行路由跳转 - 通过JS代码逻辑(事件)进行跳转 --
useNavigate
函数组件中跳转的实现
使用
useNavigate
const nav = useNavigate();
const handleSubmit = (v) => {
nav('/home');
}
类组件中跳转的实现
核心还是
useNavigate但是需要高阶组件进行处理
import {useNavigate} from "react-router-dom"
export default const withRouter = (WrapperComponent) => {
return props => {
const navigate = useNavigate();
return <WrapperComponent {...props} router={{navigate}}/>
}
}
路由参数的传递
有两种方式,分别为:动态路由,search传递参数
动态路由
// 1.配置Route
<Route path="/detail/:id" element={<Detail />} />
// 2.参数传递
<Link to="detail/123">detailView</Link>
search参数传递
// 1.传递
<Link to="detail?name=123&age=321">detailView</Link>
// 2.接收
const [searchParams] = useSearchParams();
const query = Object.fromEntries(searchParams.entries());
// 3.使用
{query.age}
项目中的配置
// 挂载路由占位视图
{useRoutes(routes)}
// 配置routes
import React from "react";
import { Navigate } from "react-router-dom";
const Login = React.lazy(() => import("@/views/login-view/index.jsx"));
const Home = React.lazy(() => import("@/views/home-view/index.jsx"));
const FormView = React.lazy(() => import("@/views/form-view/index.jsx"));
const TableView = React.lazy(() => import("@/views/table-view/index.jsx"));
const RichTextView = React.lazy(() => import("@/views/rich-text-view/index.jsx"));
const Other = React.lazy(() => import("@/views/other-view/index.jsx"));
const routes = [
{
path: "/",
element: <Navigate to="/login"/>,
},
{
path: "/home",
element: <Home/>,
},
{
path: "/login",
element: <Login/>,
},
{
path: "/form",
element: <FormView />,
},
{
path: "/table",
element: <TableView />,
},
{
path: "/richText",
element: <RichTextView />,
},
{
path: "/other",
element: <Other />,
},
];
export default routes;
// 根组件中配置路由模式和suspense
import { HashRouter } from "react-router-dom";
import LoadingView from "@/views/loading-view/index.jsx";
<HashRouter>
<Suspense fallback={<LoadingView />}>
<Provider store={store}>
<App />
</Provider>
</Suspense>
</HashRouter>