路由在Vue2和React中的应用

137 阅读6分钟

路由的定义

维护一个映射表 => 决定数据的流向

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

步骤:

  1. 创建路由组件
  2. 配置路由映射,组件和路径映射的关系
  3. 使用路由,通过<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 取值
    
  • routethis.route和this.router 的区别:

    • routerVueRouter实例,想要导航到不同URL,则使用router为VueRouter实例,想要导航到不同URL,则使用router.push 方法
    • this.$route => 处于活跃的路由对象,可以获取 name,path,query,params 等

ReactRouter

react-router: 6版本

在 Web 应用中进行路由管理,需要使用 react-router-dom

路由模式的选择

BrowserRouterHashRouter
使用 HTML5 的 history API 来实现路由管理。它使用正常的 URL 形式,例如 https://example.com/about,并在浏览器中使用 pushStatepopState 事件来实现 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.处理路由导航

LinkNavLink
用于在应用程序中导航到不同的路由用于在应用程序中呈现主菜单或导航栏。
不会在链接上添加任何额外的类或样式,这使得它比 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页的配置

  1. 开发一个Not Found组件页面
  2. 配置对应的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>

手动路由跳转的实现

路由跳转的方式:

  1. 通过Link或者NavLink进行跳转
  2. 通过Navigate组件的方式进行路由跳转
  3. 通过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>

更新中