Vue learn Vue-Router


Vue-Router

1、SPA 的理解

  • 单页Web应用(single page web application,SPA)。
  • 整个应用只有一个完整的页面
    • 点击页面中的链接不会刷新页面,只会做页面的局部更新。
  • 数据都需要通过ajax请求获取, 并在前端异步展现。

2、vue-router

组件分类:(只是从文件夹名称上划分)

  • 一般组件:components
  • 路由组件:pages

安装 vue-router

npm install vue-router

需要将路由放到 @/src/router/index.js

应用了 vue-router 后, vc 和 vm 身上会出现 $router 和 $route

  • 一个路由就是一个映射关系(key:value)
  • key 为路径,value 可能是 function 或者 component

补充:后端路由和前端路由

  1. 后端路由
    • 理解: value是function, 用来处理客户端提交的请求。
    • 注册路由: router.get(path, function(req, res))
    • 工作过程:当服务器接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
  2. 前端路由
    • 浏览器端路由,value是component,用于展示页面内容
    • 注册路由: <Route path="/test" component={Test}>
    • 工作过程:当浏览器的path变为/test时, 当前组件就会变为Test组件

3、前端路由

前端路由的核心,就在于——改变视图的同时不会向后端发出请求

前端路由的诞生

前端路由的出现要从 ajax 开始,有了 Ajax 后,用户交互就不用每次都刷新页面,体验带来了极大的提升。随着技术的发展,简单的异步已经不能满足需求,所以异步的更高级体验出现了——SPA(单页应用)。 SPA 的出现大大提高了 WEB 应用的交互体验。在与用户的交互过程中,不再需要重新刷新页面,获取数据也是通过 Ajax 异步获取,页面显示变的更加流畅。 但由于 SPA 中用户的交互是通过 JS 改变 HTML 内容来实现的,页面本身的 url 并没有变化,这导致了两个问题:

  • SPA 无法记住用户的操作记录,无论是刷新、前进还是后退,都无法展示用户真实的期望内容。
  • SPA 中虽然由于业务的不同会有多种页面展示形式,但只有一个 url,对 SEO 不友好,不方便搜索引擎进行收录。

前端路由就是为了解决上述问题而出现的。

什么是前端路由

简单的说,就是在保证只有一个 HTML 页面,且与用户交互时不刷新和跳转页面的同时,为 SPA 中的每个视图展示形式匹配一个特殊的 url。在刷新、前进、后退和SEO时均通过这个特殊的 url 来实现。 为实现这一目标,我们需要做到以下二点:

  • 改变 url 且不让浏览器像服务器发送请求。
  • 可以监听到 url 的变化

接下来要介绍的 hash 模式和 history 模式,就是实现了上面的功能。

1、hash 模式

原理

  • 早期的前端路由的实现就是基于 location.hash 来实现的,location.hash 的值就是URL中#后面的内容 其实现原理就是监听#后面的内容来发起Ajax请求来进行局部更新,而不需要刷新整个页面。

  • 使用 hashchange 事件来监听 URL 的变化,以下这几种情况改变 URL 都会触发 hashchange 事件:浏览器前进后退改变 URL、a 标签改变 URL、window.location 改变URL。

//html
<ul id="menu">
  <li>
    <a href="#index">首页</a>
  </li>
  <li>
    <a href="#news">资讯</a>
  </li>
  <li>
    <a href="#user">个人中心</a>
  </li>
</ul>
<div id="app"></div>

//js
function hashChange(e){
    let app = document.getElementById('app')
    switch (location.hash) {
      case '#index':
        app.innerHTML = '<h1>这是首页内容</h1>'
        break
      case '#news':
        app.innerHTML = '<h1>这是新闻内容</h1>'
        break
      case '#user':
        app.innerHTML = '<h1>这是个人中心内容</h1>'
        break
      default:
        app.innerHTML = '<h1>404</h1>'
    }
}
window.onhashchange = hashChange
hashChange()

优点

  • 兼容低版本浏览器,Angular1.x和Vue默认使用的就是hash路由

  • 只有#符号之前的内容才会包含在请求中被发送到后端,也就是说就算后端没有对路由全覆盖,但是不会返回404错误

  • hash值的改变,都会在浏览器的访问历史中增加一个记录,所以可以通过浏览器的回退、前进按钮控制hash的切换 会覆盖锚点定位元素的功能

缺点

  • 不太美观,#后面传输的数据复杂的话会出现问题

2、history 模式

原理

  • history 提供了 pushState 和 replaceState 两个方法来记录路由状态,这两个方法改变 URL 不会引起页面刷新

  • history 提供类似 hashchange 事件的 popstate 事件,但 popstate 事件有些不同:通过浏览器前进后退改变 URL 时会触发 popstate 事件,通过 pushState/replaceState 或a标签改变 URL 不会触发 popstate 事件。好在我们可以拦截 pushState/replaceState的调用和a标签的点击事件来检测 URL 变化,所以监听 URL 变化可以实现,只是没有 hashchange 那么方便。

  • pushState(state, title, url) 和 replaceState(state, title, url) 都可以接受三个相同的参数。

//html
<ul id="menu">
  <li>
    <a href="/index">首页</a>
  </li>
  <li>
    <a href="/news">资讯</a>
  </li>
  <li>
    <a href="/user">个人中心</a>
  </li>
</ul>
<div id="app"></div>

//js
document.querySelector('#menu').addEventListener('click',function (e) {
  if(e.target.nodeName ==='A'){
    e.preventDefault()
    let path = e.target.getAttribute('href')  //获取超链接的href,改为pushState跳转,不刷新页面
    window.history.pushState({},'',path)  //修改浏览器中显示的url地址
    render(path)  //根据path,更改页面内容
  }
})

function render(path) {
  let app = document.getElementById('app')
  switch (path) {
    case '/index':
      app.innerHTML = '<h1>这是首页内容</h1>'
      break
    case '/news':
      app.innerHTML = '<h1>这是新闻内容</h1>'
      break
    case '/user':
      app.innerHTML = '<h1>这是个人中心内容</h1>'
      break
    default:
      app.innerHTML = '<h1>404</h1>'
  }
}
window.onpopstate = function (e) {
  render(location.pathname)
}
render('/index')

优点

  • 使用简单,比较美观

  • pushState()设置新的URL可以是任意与当前URL同源的URL,而hash只能改变#后面的内容,因此只能设置与当前URL同文档的URL

  • pushState()设置的URL与当前URL一模一样时也会被添加到历史记录栈中,而hash#后面的内容必须被修改才会被添加到新的记录栈中

  • pushState()可以通过stateObject参数添加任意类型的数据到记录中,而hash只能添加短字符串

  • pushState()可额外设置title属性供后续使用

缺点

  • 前端的URL必须和向发送请求后端URL保持一致,否则会报404错误
  • 由于History API的缘故,低版本浏览器有兼容性问题

3、两种不同的使用场景

  • 从上文可见,hash模式下url会带有#,当你希望url更优雅时,可以使用history模式。

  • 当使用 history 模式时,需要注意在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。

  • 当需要兼容低版本的浏览器时,建议使用hash模式。

  • 当需要添加任意类型数据到记录时,可以使用 history 模式。

使用 history 模式可以看官网

https://router.vuejs.org/zh/guide/essentials/history-mode.html

MDN

https://developer.mozilla.org/zh-CN/docs/Web/API/History/pushState

结合自身例子,对于一般的 Vue + Vue-Router + Webpack + XXX 形式的 Web 开发场景,用 history 模式即可,只需在后端(Apache 或 Nginx)进行简单的路由配置,同时搭配前端路由的 404 页面支持。

4、vue-router 使用方式

第一步:创建 路由器

/* 该文件是 Vue 中的路由器文件,路由器管理所有路由 */
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from './pages/Home.vue'
import About from './pages/About.vue'


Vue.use(VueRouter)

// 创建一个路由器管理所有路由
const router = new VueRouter({
  // model: '', 可以选择路由模式
  // 路由数组
  routes: [
    {
      path: '/home',
      component: Home
    },
    {
      path: '/about',
      component: About
    }
  ]
})

// 暴露路由器
export default router

第二步:main.js 中引入

import Vue from 'vue'
import App from './App.vue'
import router from './router/index'

Vue.config.productionTip = false

new Vue({
  el: '#app',
  router,
  render: h => h(App)
})

第三步:页面使用

需要的标签:

  • <router-link/> :对 html 原生 a 标签做了封装
    • Vue 特有属性:active-class 解决激活链接后的样式问题
  • <router-view/>:展示路由的显示视图

小提示:

5、一个路径匹配多个组件

例如 /home 中显示两个组件 Home 和 Home2

使用 components

// 创建一个路由器管理所有路由
const router = new VueRouter({
  // model: '', 可以选择路由模式
  // 路由数组
  routes: [
    {
      path: '/home',
      components: {
        h1: Home,
        h2: Home2
      }
    },
    {
      path: '/about',
      component: About
    }
  ]
})

<router-view/> 需要做特殊处理

<!-- 路由视图 -->
<!-- 默认占位 -->
<router-view></router-view>

<!-- 一个路径使用多个组件 -->
<router-view name="h1"></router-view>
<router-view name="h2"></router-view>

6、二级和三级路由

二级路由

// 创建一个路由器管理所有路由
const router = new VueRouter({
  // model: '', 可以选择路由模式
  // 路由数组
  routes: [
    {
      path: '/home',
      component: Home,
      children: [
        {
          path: 'message',
          component: Message
        },
        {
          path: 'news',
          component: News
        }
      ]
    },
    {
      path: '/about',
      component: About
    }
  ]
})

三级路由

和二级路由的原理一样,加 children

7、路由传参

  • params
  • query

TODO学习 Vue 的路由传参方式

1、params 传参

  • 第一步:路由表中声明接收传入的参数

    children: [
        {
            path: 'message',
            component: Message,
            children: [
                {
                    // 声明接收 params 参数
                    path: 'detail/:id/:title/:content',
                    component: Detail
                }
            ]
        },
        {
            path: 'news',
            component: News
        }
    ]
    },
  • 第二步:跳转路由时携带参数

    <li v-for="msg in messageArr" :key="msg.id">
        <!-- 切换路径时,携带 params 参数 -->
        <router-link
                     :to="`/home/message/detail/${msg.id}/${msg.title}/${msg.content}`"
                     >{{ msg.title }}</router-link>
    </li>
  • 第三步:接收参数

    <ul>
        <!-- 获取 params 参数 -->
        <li>ID: {{ $route.params.id }}</li>
        <li>TITLE:{{ $route.params.title }}</li>
        <li>CONTENT:{{ $route.params.content }}</li>
    </ul>

2、query 传参

  • 第一步:路由表不需要声明接收的参数

  • 第二步:路由跳转时,携带 query 参数

    <!-- 路由切换时,携带 query 参数 -->
    <router-link
                 :to="`/home/message/detail/?id=${msg.id}&title=${msg.title}&content=${msg.content}`"
                 >{{ msg.title }}</router-link>&nbsp;&nbsp;
  • 第三步:接收 query 参数

    computed: {
        id() {
        	return this.$route.query.id
        },
        title() {
        	return this.$route.query.title
        },
        content() {
        	return this.$route.query.content
        }
    }

3、同时接收 params 和 query 参数

同时使用的时候,路由表中必须声明 params 的占位符,同时以 params 传递的参数一定会第一个被接收

4、参数相关的其他知识点

在很多情况下,我们都需要用户的请求地址携带一些参数,然后通过这个参数再进行查询。

而且很有可能在今后的<router-link>以及methods中都要使用这个参数,如何操作?

{path: "/book/:id(\\d+)", component: book},  // 转义 \d+
{path: "/book/:id?", component: book},  // ?代表可以有也可以没有  

8、命名路由

可以使用命名路由简化路由跳转和路由传参

  • 第一步:路由表中为路由起一个名字

    children: [
        {
            path: 'message',
            component: Message,
            children: [
                {
                    // 声明接收 params 参数
                    // path: 'detail/:id/:title/:content',
                    // 声明接收 query 参数
                    // path: 'detail', // query 参数无需声明即可接收
    
                    // 同时使用 params 和 query
                    // 要先声明 params, 一定先接收 params
                    path: 'detail/:id',
                    name: 'xiangqing', 
                    component: Detail
                }
            ]
        },
  • 第二步:跳转路由时传入对象

    <!-- 命名路由 -->
    <router-link 
                 :to="{
                      name:'xiangqing', 
                      params: {id: msg.id}, 
                      query: {title: msg.title, content: msg.content}
                      }">
        {{ msg.title }}
    </router-link>
  • 第三步:接收参数和之前一样

9、路由的 props 配置(常用)

为了继续简化路由传参和接收参数,可以使用 路由的 props 属性

共有三种写法:

  • 第一步:声明 props 属性

    children: [
        {
            // 声明接收 params 参数
            // path: 'detail/:id/:title/:content',
            // 声明接收 query 参数
            // path: 'detail', // query 参数无需声明即可接收
    
            // 同时使用 params 和 query
            // 要先声明 params, 一定先接收 params
            path: 'detail/:id/:title/',
            name: 'xiangqing', 
            // props: { title: '123naivekyo'},    // 通过 props 映射自定义的静态数据给当前路由
            // props: true,  // 只映射 params 参数为 props 传给路由组件
            // 函数形式, 参数为 route
            props(route) {  // 此处的 route 是 vc 或 vm 身上的 $route
    
                const { id, title } = route.params
                const { content } = route.query
                // 返回对象
                return { id, title, content }
            },
            component: Detail
        }
    ]
  • 三种方式:

    • 映射自定义静态数据
    • props:true:注意该方式只能传递 params 中的参数,不用传递 query 参数
    • props(route) {},注意最后函数方式最常用,该函数传入的参数是 vm.$route 属性,可以拿到很多有用的数据,而它的返回值就是 key-value 的 props
  • 第二步:路由跳转,和之前一样

  • 第三步:接收参数,使用组件的 props 接收路由参数,展示就比较方便了

    <script>
      export default {
        name:'Detail',
        props: ['id', 'title', 'content']
      }
    </script>

10、编程式路由导航

<router-link/> 的两个属性 :replace 和 push

这也是两种路由的方式:

  • replace:不会在 window.history 中留下痕迹
  • push :会留下痕迹,可以回退

补充

React 传参:

  • params
  • search(相当于 Vue 的 query)
  • location.state(URL 不显示路由参数)

Vue 传参:

  • params

  • query

  • 如果想要不显示 url 中携带的参数信息,可以使用一个 Vue 路由的插件

    • 补充:js 库 qs

      若后端使用@RequestParam 来接收前端传过来的参数的,Content-Type要设置为application/x-www-form-urlencoded,并且需要对data使用qs.stringify来进行转换;

      若后端使用@RequestBody 来接收前端传过来的参数的,Content-Type要设置为application/json;

    • 传参方式在 axios 中有 get、post、body

编程式路由导航:

methods: {
  pushShow(msg) {
    this.$router.push({ name: 'xiangqing', params: {
      id: msg.id,
      title: msg.title,
      content: msg.content
    }})
  },
  replaceShow(msg) {
    this.$router.replace({
      name: 'xiangqing',
      params: {
        id: msg.id,
        title: msg.title,
        content: msg.content         
      }
    })
  }
},

11、缓存路由组件

使用 <keep-alive>

https://cn.vuejs.org/v2/api/#keep-alive

12、导航守卫

参考官网:https://v3.router.vuejs.org/zh/guide/advanced/navigation-guards.html


Author: NaiveKyo
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source NaiveKyo !
  TOC