最开始我们就将到了,锚链接的特点是:会在地址栏上产生浏览历史。所以对于的<router-link>也会有同样的效果。

  但是,vue-router 为<router-link>封装了一些 props 属性,以增强它的灵活性。

官网:<router-link> Props (opens new window)

# 历史记录

  • 默认

会调用 $router.push(),并且产生历史记录

  • replace

会调用 $router.replace(),不会产生历史记录

应用思考

  当有多级路由嵌套时,直接使用可以会造成历史回退困难,此时我们可以直接对一级路由进行 replace 处理。

<!-- replace属性:防止产生历史记录 -->
<router-link to="/home" replace>Home</router-link>
<router-link to="/about" replace>About</router-link>

# 一、路由导航

  上面我们使用的 replace 属性,本质上讲就是帮我们调用的$router.replace()。

# 1、编程式导航

  通俗的将就是没有直接通过点击<router-link>进行页面跳转的,就是编程式(API 式)的。

如上面的:$router.push()$router.replace()

提示

对应原生 js 中的: window.location.href = 'new_url'window.location.replace('new_url')

  那使用编程式的用什么好处呢?

答案 🍗
  • 可以让链接跳转的功能不仅仅局限<a>标签
  • 可以通过 js 自定义页面(路由)跳转的时机、方式等
以前:声明式写法
<ul class="nav">
  <li v-for="item in titleList">
    <router-link
      :to="
      {
        name: 'part1',
        params: {
          title: item.title
        }
      }"
    >
      part1
    </router-link>
  </li>
</ul>
现在:编程式导航
<ul class="nav">
  <li v-for="item in titleList">
    <!-- 可以使用button标签了、触发时机可以自定义了 -->
    <button @click="pushLink(item)">part1</button>
  </li>
</ul>
export default {
  methods: {
    pushLink(item) {
      this.$router.push({
        name: item.name,
        params: {
          title: item.title
        }
        // path: item.path,
        // query: {
        //   title: item.title
        // }
      })
    }
  }
}

# 2、API 使用

① 页面(路由)跳转

  除了前面已经提到的导航 API 外,

router.push(location, onComplete?, onAbort?)
router.push(location).then(onComplete).catch(onAbort)

router.replace(location, onComplete?, onAbort?)
router.replace(location).then(onComplete).catch(onAbort)

② 历史回滚

  还有:

  • $router.go(n)用于快速的历史前进、后退(通常用得少)。
  • $router.back()用于后退一步
  • $router.forward()用于前进一步

提示

对应原生 js 中的: window.history.back()window.history.forward()window.history.go(n)

<button @click="$router.back()">后退</button>

<button @click="$router.forward()">前进</button>

# 3、导航缓存

  对于路由导航,有没有必要像动态路由/嵌套路由的keep-alive一样,搞一下路径缓存之类的操作,用于提升用户的体验。

  之前没有想过,但在接触了 vue-element-admin (opens new window) 后台模板项目后,我感觉是有必要的。

用户退出后,重新进入后台管理时,直接跳到上次退出前的使用页面。

代码实现
router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !auth.isAuthenticated()) {
    next({
      path: '/login',
      query: { redirect: to.fullPath }
    })
  } else {
    next()
  }
})

# 二、导航守卫

官方地址:

https://v3.router.vuejs.org/zh/guide/advanced/navigation-guards.html

# 1、基础认知

  • 变化角度

  -参数或查询的改变并不会触发进入/离开的导航守卫。你可以通过观察 $route 对象来应对这些变化,或使用 beforeRouteUpdate 的组件内守卫。

<script>
  export default {
    // 监听$route对象
    watch: {
      $route: function () {
        //
      }
    },

    // 在当前路由改变,但是该组件被复用时调用
    beforeRouteUpdate(to, from, next) {
      // 例如:对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候
    }
  }
</script>
  • 作用角度

  vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航

  • 触发角度

  初始化的时候、发生路由切换的时候

# 2、beforeEach()

全局前置守卫

基础结构:

router.beforeEach((to, from, next) => {
  // to表示将要进入的目标 路由对象
  console.log(to)

  // from表示当前导航正要离开的 路由对象
  console.log(from)

  // 触发 resolve 钩子(表示放行)
  next()
})

next 回调:

  如下图所示,next 函数的 3 种调用方式最终会导致不同的结果:

通常情况下,路由对象 to 和 from 的使用如下:to.path、to.name、to.meta.requiresAuth 等

// 设置全局前置守卫
router.beforeEach((to, from, next) => {
  // 1、简化版
  const token = localStorage.getItem('token')

  if (to.path.startWith('/main') && !token) {
    next('login')
  }

  // 2、详细版
  // 是否进入后台
  // if (to.path.startWith('/main')) {
  //   const token = localStorage.getItem('token')

  //   // 身份是否合法
  //   if (token) {
  //     next()
  //   } else {
  //     next('/login') //  当前的导航被中断,然后进入一个新的导航
  //   }
  // } else {
  //   next(false) //  URL 地址会重置到 from 路由对应的地址
  // }
})
export default router
开发应用:后台 page 路径不规则时 ✍
  • router/index.js(从 路径 角度)

可以采用白名单,也可以采用 to.meta.requiresAuth

// 白名单页面
const whiteList = ['/login', '/reg']

// 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
router.beforeEach((to, from, next) => {
  if (whiteList.includes(to.path)) {
    next()
  } else {
    let token = localStorage.getItem('Authorization')

    if (token === null || token === '') {
      next('/login')
    } else {
      next()
    }
  }
})

或者:

  • router/index.js(从 token 角度)

不推荐,因为不但嵌套的两层 if、并且白名单页面还可以重复做了两次判断

// 无需验证token的页面
// 【因为登录、注册页面也没有token,根据全局前置守卫的定义不做白名单处理,会一直处于跳转——触发——跳转循环中】
const whiteList = ['/login', '/reg']

router.beforeEach((to, from, next) => {
  // 和前面获取token的方式相比,其实vuex🚩默认也是从Application中取的
  const token = $store.state.token

  // 如果有token, 证明已登录
  if (token) {
    next()
  } else {
    // 白名单✨页面放行
    if (whiteList.includes(to.path)) {
      next()
    } else {
      // 如果其他页面请强制拦截并跳转到登录页面
      next('/login')
    }
  }

  next()
})

# 3、afterEach()

全局后置钩子

  前置守卫,通常用于发生路由跳转前进行权限验证。但有时候,我们也需要一些操作必须在发生跳转后执行。

例如:document.title (确保真正的跳转完成了才设置)

router.afterEach((to, from) => {
  document.title = to.meta.title || 'lencamo'
})

# 4、守卫分类

  • 路由独享守卫

beforeEnter

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})
  • 组件内的守卫

beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave

<script>
export default {
  // 通常用来禁止用户在还未保存修改前突然离开
  beforeRouteLeave(to, from, next) {
    const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
    if (answer) {
      next()
    } else {
      next(false)
    }
  }
}
</script>

# 5、导航解析流程

阶段 事件描述
导航被触发
【beforeRouteLeave ✨ 守卫】 在失活的组件里调用
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~
解析异步路由组件
beforeEach 🎈 全局调用
【beforeRouteUpdate 守卫(2.2+) 】 在重用的组件里调用
beforeEnter 在路由配置里调用
解析异步路由组件
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~
【beforeRouteEnter 守卫】 在被激活的组件里调用
beforeResolve(2.5+) 全局调用
导航被确认
afterEach ✨ 全局调用
触发 DOM 更新

# 三、router 构造 ✨

# 1、router 实例属性

亲身实践:history 模式下页面刷新 404 问题 (opens new window)

export default new VueRouter({
  mode: 'history', // 默认hash模式

  // 根据下面内容自个体会😂
  // history模式时:ip:端口/oj/home
  // hash模式时:ip:端口/oj/#/home
  base: '/oj'
  // 打包之后的文件项目是放在服务器根目录下的 oj文件夹下的,而不是直接放在根目录
})

# 2、history 模式解读

区别:

指标 hash history
url 显示 有#,不美观 无#,很 nb
支持版本 支持低版本浏览器 HTML5 新特性 API
请求地址 hash 前面的部分 地址栏中的整个地址
回车刷新(发起页面请求) 加载对应页面 可能 404 页面

history 的 404 页面 问题:

  前端打包后的 dist 包中,只有 index.html

  所以,部署上线后,history 模式下发送的非根路径的页面请求,服务端是找不到的。

关于 404 Not found 前端预处理
  • index.js
const router = new VueRouter({
  mode: 'history',
  routes: [
    {
      path: '/404',
      component: () => import('@/views/error-page/404'),
      hidden: true
    },

    // 含有通配符的路由应该放在最后 !!!
    { path: '*', component: 'NotFound' }

    // 当使用一个通配符时,$route.params 内会自动添加一个名为 pathMatch 参数。它包含了 URL 通过通配符被匹配的部分
    // { path: '/:pathMatch(.*)', component: 'NotFound' }
    // { path: '/:pathMatch(.*)*', component: 'NotFound' }
  ]
})
  • NotFound.vue
<template>
  <div>
    <h2>NotFound:你当前的路由地址{{ $route.params.pathMatch }}匹配失败,请输入正确的路由地址!</h2>
  </div>
</template>

# 3、部署问题

  上面已经提到了 history 模式下,部署上线可能会存在的潜在问题。

前端临时解决:(开发环境)

官方:vue-cli (opens new window)webpack (opens new window)

module.exports = {
  publicPath: '/',
  devServer: {
    // 解决history模式下,服务器请求不到资源返回404问题
    // historyApiFallback: true
    historyApiFallback: {
      // index: '/oj/index.html' // 加不加.与publicPath一致
      rewrites: [
        {
          from: /.*/g,
          to: '/oj/index.html'
        }
      ]
    },
    proxy: {}
  }
}

后端根本解决:(生产环境 (opens new window)

express 部署

  可以在本地部署一个简单的路由切换案例试一试 😂

使用 connect-history-api-fallback (opens new window) 中间件

var express = require('express')

var app = express()
app.use(history())
nginx 部署
# /conf/nginx.conf
user root

http{
  # 项目一
  server {
    listen 80;
    server_name  localhost;
    root /www/wwwroot/

    # 代理/oj,而不是/是因为前端使用了base: '/oj',
    location /oj {
      root  /www/wwwroot/oj;
      index  index.html index.htm;

      # 配置url🎈重写语句(先返回404,然后返回index.html)
      try_files $uri $uri/ /oj/index.html;
    }
  }
}

分析:

  • 浏览器请求 http://localhost/oj/problem, nginx 发现没有资源,所以返回 404

  • nginx 紧接着又将 index.html 返回,浏览器加载后,又去加载其中引用的 js、css 等文件,然后路由规则根据地址栏中的地址,去匹配对应的组件进行渲染

更新于 : 8/7/2024, 2:16:31 PM