什么是路由
- 路由是一个网络工程中的一个术语
- 路由(routing) 就是通过互联的网络把信息从源地址传输到目的地址的活动
- 路由器提供了两种机制: 路由和传送
- 路由是绝对数据包从来源到目的地的路径
- 传送将输入端的数据转移到合适的输出段
- 路由中有一个非常重要的概念叫路由表
- 相关概念
- 内网IP, 每一个链接上网络的设备都有一个内网IP, 这个内网IP是为了区分链接同一个局域网的不同设备, 在同一个局域网中的每个设备的内网IP是唯一的
- 公网IP, 公网IP是一个局域网对外链接是用于区分不同局域网的, 每一个局域网的公网IP都是唯一的
- 路由中有一个映射表, 将局域网中的内网IP和设备的mac地址对应起来
- 举个例子
- 现在有一条信息从北京发到广东
- 这条信息途中会进过很多路由, 并根据公网IP找到对应的局域网
- 再通过路由中的映射表, 找到要传送的设备mac地址对应的内网IP, 再传送给正确的设备
认识web开发的三个阶段
后端路由阶段
- 在早期的网站开发中, 整个HTML页面都是由服务器渲染出来的
- 用户在浏览器中输入URL后, 浏览器向服务器发出请求
- 服务器直接生产整个渲染好的html页面, 返回给客户端进行展示
- 一个网站有那么多个页面, 服务器是怎么处理的呢?
- 一个页面对应一个URL
- URL会发送给服务器, 服务器通过正则对URL进行匹配, 并且最后交给一个Controller进行处理
- Controller进行各种各样的处理后, 将最终生成的HTML页面, 返回给前端
- 这就完成了一个IO(input/output)操作
- 以上的操作就是后端路由(也称为后端渲染)
- 当页面需要请求不同的页面路径内容的时候, 全部交给后端服务器处理, 后端服务器渲染好整个页面后, 将页面返回给客户端
- 这种情况下渲染好的页面, 不需要单独加载任何的js和css, 因为在后端服务器已经加载完毕了, 可以直接交给浏览器展示, 这样的操作有利于SEO(搜索优化)
- 后端路由(后端渲染)的缺点
- 整个页面的模块由后端人员来编写和维护
- 前端开发人员如果要开发页面, 需要通过PHP和java等语言来编写页面代码
- 而且通常情况下, HTML代码和数据以及它们的对应逻辑会混合在一起, 编写和维护都是非常糟糕的事情
- 总流程就是, 后端路由就是, 客户端输入URL请求页面, 服务器端接收URL, 服务器端处理URL和页面的映射关系, 服务器端通过java从数据库中读取数据结合html和css加载整个页面, 将整个页面返回给客户端
前后端分离阶段
- 随着Ajax的出现, 就有了前后端分离的开发模式
- 前端通过Ajax请求数据, 后端提供API来返回数据, 前端通过JavaScript将返回来的数据渲染到页面中
- 这样做的最大优点就是将前后端的责任清晰化, 前端专注于交互和可视化, 后端专注于数据
- 并且在移动端(IOS/Android)出现后, 后端不需要再开发其他的接口, 依然使用之前的一套API即可
- 目前很多网站都是采用这种开发模式
- 总流程是, 前后端分离就是, 客户端输入URL请求页面的时候, 服务器会先从静态资源服务器中返回html+css+js, 然后在前端使用js代码通过Ajax请求API数据, 服务器端再提供API接口服务, 在前端接收到API数据后渲染页面
- 大部分代码都是在前端执行并渲染的, 所以叫前端渲染
单页面富应用(SPA)阶段
- 其实SPA最主要的特点就是在前后端分离的基础上加了一层前端路由
- 也就是前端来维护一套路由规则
- 静态资源服务器中的html+css+js代码只有一套, 这一套代码包含了很多的组件, 用户在客户端请求页面的时候, 会将这一套代码全部返回给客户端, 用户在该页面请求其他页面的时候, 通过前端路处理URL和页面映射关系, 然后通过Ajax请求API数据, 实现显示不同的组件
一些概念问题
- 如何区分前端渲染和后端渲染
- 前端渲染就是大部分代码都是在前端执行并且渲染
- 后端渲染是指客户端请求页面时, 后端服务器渲染整个页面并返回,称为后端渲染
- 什么是前后端分离
- 前端负责页面交互和可视化, 通过Ajax来请求数据, 后端专注于数据
- 什么是前端路由,什么是后端路由
- 前端路由是指, 在前端处理URL和页面的映射关系
- 后端路由是指, 在客户端发送URL请求后, 在后端服务器处理URL和页面的映射关系
前端路由的核心: 改变URL, 但是页面不进行整体的刷新
- 通过改变URL的hash值
- URL的hash也就是锚点(#), 本质是改变window.loaction和href属性
- 我们可以通过直接复制loaction.hash来改变href, 但是页面不发生刷新
- 通过HTML5的history模式: pushState
- history接口时HTML5新增的, 它有五种模式改变URL而不刷新页面
- history.pushState(state, title, url), 通过栈结构压入一个新的URL
- history.replaceState(), 修改当前的 history 实体。通过这个接口改变的URL, 不能使用history.back() 后退历史记录
- history.go(num) 前进或者后退指定数量历史记录 num可以时负数, 代表后退
- history.back() 后退, 相当于history.go(-1)
- history.forward() 前进, 相当于history.go(1)
vue-router的安装
- 我们已经学习过webpack了, 后续开发都是通过工程化的方式进行开发
- 步骤一: 安装路由vue-router (在使用脚手架初始化项目的时候, 会询问是否安装vue-router, 如果安装过了可以在package.json中看到已经有vue-router模块了, 那就不需要再安装了, 如果当时没有安装, 那么现在就可以用npm安装)
- 在当前项目目录下, 命令行输入 npm install vue-router --save(注意时运行时依赖安装)
- 步骤二: 在模块化工程中使用路由(因为vue-router是一个插件, 所以要通过Vue.use()来安装路由功能)
- 第一步: 在src文件夹中的router文件夹中的index.js文件中(遵循模块化管理代码), 导入vue-router和vue, 并且调用Vue.use()来安装路由功能
- 第二步: 创建路由实例, 并且编写路由映射配置
- 第三步: 在Vue实例中挂载创建的路由实例
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
const routes = [
]
const router = new VueRouter({
routes
})
export default router;
import router from './router/index'
new Vue({
router
})
vue-router的使用
- 第一步: 创建路由组件, 因为路由时一个一个的url和组件的映射关系, 所以先创建好路由组件
- 第二步: 配置路由映射, 在router文件夹中的index.js 中配置组件和路径的映射关系
- 第三步: 在根组件中, 通过标签 router-link 和 router-view 使用路由
- router-link>: 这个标签是一个vue-router中内置的组件, 它最终会被渲染成一个 a 标签
- router-view>: 这个标签会根据当前的路径, 动态渲染出不同的组件
- 网页的其他内容, 比如顶部的标题/导航, 或者底部的一些版权信息等等回合 router-view> 处于同一个等级
- 在路由切换的时候, 切换的是 router-view> 中挂载的组件, 其他内容是不会发生改变的
<template>
<div>
<h2>{{ message }}</h2>
<p>我是Home内容</p>
</div>
</template>
<script>
export default {
name: "Home",
data() {
return {
message: "这里是首页",
};
},
};
</script>
<template>
<div>
<h2>{{ message }}</h2>
<p>我是关于里面的内容</p>
</div>
</template>
<script>
export default {
name: "About",
data() {
return {
message: "这里是关于",
};
},
};
</script>
import Home from "../components/Home.vue";
import About from "../components/About.vue";
const routes = [
{
path: "/home",
component: Home
},
{
path: "/about",
component: About
}
]
// 在根组件中的template中使用<router-link> 和 <router-view> 来使用路由
<template>
<div id="app">
<router-link to="/home">首页router-link> // 这个标签会根据 to属性中的值来修改 url, 此时<router-view>显示对应的组件
<router-link to="/about">关于router-link>
<router-view>router-view> // 这个标签是用来决定组件显示的位置的
div>
template>
路由的默认路径
- 在通常情况下, 我们输入网站URL, 进入网站的首页, 我们希望 router-view 直接渲染出首页的内容
- 但是现在, 默认是没有显示首页的组件内容的, 而是要用户点击了首页的 router-link 标签才会显示
- 那么如何让路径默认跳转到首页, 而且 router-view 渲染首页组件呢?
- 非常简单, 只要在路由配置中多配置一个映射就可以了
- 配置内容为 path: “/” 这里的意思是, 配置根路径
- redirect : “/name” redirect翻译为重定向, 就是重新定义方向
- 配置解析
- 在routes中新配置一个映射
- path 配置的是一个根路径 “/”
- redirect是重定向, 表示我们将根路径重新定向到 /home 的路径下,这样我们就可以等我们想要的结果了
const routes = [
{
path : "/",
redirect : "/home"
}
]
HTML5的History模式
- 通过改变hash值来改变网页的URL, 地址末尾会有一个/# 不好看
- 如果通过HTML5的history模式来改变网页的URL, 地址就会好看很多
- 只要进行对路由配置文件进行简单的配置即可
- 在路由配置文件中那个的router实例中添加一个option
- mode : “history” 其实这个option值默认是hash, 只是我们手动把它改成history而已
const router = new VueRouter({
routes,
mode : "history"
})
router-link 标签的补充
- 在前面我们只用了 to 这个属性来指定跳转的路径
- router-link 还有一些其他的属性
- tag : router-link标签默认最终会渲染成 a 标签, 通过tag 属性, 我们可以修改router-link标签最终会渲染成什么标签, 比如设置 tag=“button”, 则该router-link标签最终会被渲染成button标签
- replace : replace不会留下history记录, 所以指定replace的情况下, 后退键返回不能返回到上一个路径, 使用方法: 直接给router-link标签添加replace属性即可, 这个属性没有值
- active-class: 当router-link标签对应的路由匹配成功时, 会自动给当前标签设置了一个router-link-active的class类名, 我们可以通过这个类名修改活动的router-link标签的样式, 设置active-class可以修改这个router-link-active这个默认类名
- 关于router-link-active类, 在进行高亮显示的导航菜单或者底部tabbar时, 会使用这个类
- 一般不会修改这个默认的类名, 直接使用默认的router-link-active类名即可
- 还有另外一种方法改变router-link-active这个默认类名
- 在路由配置文件 index.js中的路由实例对象中添加 linkActiveClass 选项, 值为要修改成的类名, 也可以修改这个默认类名
<template>
<div id="app">
// 通过tag改变最终渲染成的标签
// 通过replace禁止使用后退功能
// 通过active-class改变默认的 router-link-active 类名
<router-link to="/home" tag="button" replace>首页router-link>
<router-link to="/about" tag="button" replace>关于router-link>
<router-view>router-view>
div>
template>
通过代码来实现路由跳转
- 有的时候, 页面的跳转可能需要执行对应的JS代码, 这个时候可以使用第二种跳转方式
- 本质上为跳转的标签监听事件, 通过methods方法来实现跳转
- 注意不能在方法中通过修改 history.pushState 的值来实现跳转, 这样会跳过了 vue-router 组件
- $router时vue-router 模块给每一个组件构造器添加的
- 通过 this.$router.push(“要跳转的路径”) 来实现跳转
- 如果想要禁止后退功能, 那么就将push方法改成replace方法即可
<template>
<div id="app">
<button @click="homeClick">首页button>
<button @click="aboutClick">关于button>
<router-view>router-view>
div>
template>
<script>
export default {
name: "App",
methods: {
homeClick() {
this.$router.replace("/home");
},
aboutClick() {
this.$router.replace("/about");
},
},
};
script>
动态路由
- 在某些情况下, 一个页面的path路径是不确定的, 比如我们进入用户界面的时候, 希望路径是这样的
- /user/aaa 或者 /user/bbb
- 除了前面的/user之外, 后面还跟上了用户的ID
- 这种path和component的匹配关系, 我们称为动态路由
- 如何实现动态路由
- 第一步: 在组件的路由设置中path, 通过冒号 : 表示后面的路径是不确定的
- 第二步: 使用v-bind 绑定根组件的 router-link 中的 to 属性, 使其值 = 确定的路径 + 一个从data传过来的数据
const routes = [
{
path : "/user/:userId",
component : User
}
]
<template>
<div id="app">
// 通过v-bind绑定 to 来使to可以使用组件构造器中的数据
<router-link :to="'/user/' + Id" tag="button" replace>用户router-link>
<router-view>router-view>
div>
template>
<script>
export default {
name: "App",
data() {
return {
Id: "lisi",
};
},
};
script>
传递活动的路由的数据
- 前面讲到vue-router 有一个 $router 对象, 里面有方法可以实现路径的跳转
- vue-router 中还有一个对象, 为 $route , 这个对象获取的是页面中当前活跃的路由
- $route是一个路由对象 (route object) 表示当前激活的路由的状态信息,包含了当前 URL 解析得到的信息,还有 URL 匹配到的路由记录 (route records)。
- 这个对象有很多属性, 可以查看 vue-router 的官方文档
- $route.params 这个属性是一个 key/value 对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。
<template>
<div>
<h2>{{ userId }}h2>
div>
template>
<script>
export default {
name: "User",
computed: {
userId() {
return this.$route.params.userId;
},
},
};
script>
vue打包文件解析
- 命令行输入 npm run build 后, 就会对项目进行打包
- 打包完成后项目中会生成一个新的文件夹名叫 dist
- dist文件夹解析
- static文件夹: 里面包含两个文件夹, css文件夹 和 js文件夹
- css文件夹: 里面存放的是我们的css代码
- js文件夹: 里面存放的是我们的js代码
- app.xxxx.js文件: 存放的是我们的业务代码, 也就是我们自己写的代码
- manifest.xxxxx.js文件: 存放的是底层支撑的代码, 比如说, 支撑模块和模块之间的导入导出等等
- vendor.xxxxx.js文件: 存放的是提供商也就是第三方的代码, 比如, vue的代码, vue-router的代码等等
- index.html文件: 入口HTML文件
认识路由的懒加载
- 官方解释
- 在打包构建应用的时候, JavaScript 文件会变得非常大, 会影响页面的加载
- 如果我们能把不同路由对应的组件分割成不同的代码块, 然后当路由被访问的时候才加载对应组件, 这样就更加高效了
- 通俗地说
- 首先, 我们知道路由中通常会定义很多不同的页面
- 但是这些页面最后都被打包到一个js文件中
- 那么多的页面全放在一个js文件中, 这个页面必然会很大
- 如果我们一次性从服务器请求下这个页面, 可能会花费一定的事件, 甚至用户的电脑上还出现了短暂空白的情况
- 要避免这种情况, 就要使用路由懒加载
- 路由懒加载做了什么
- 路由懒加载的主要作用就是将路由对应的组件打包成一个个的js代码块, 一个组件打包成一个js文件
- 在当这个路由被访问到的时候, 才加载对应的组件
懒加载的方式
- 每种方式的使用方法都是改变路由配置 index.js 文件中的导入组件的方式来达到懒加载
- 方式一: 结合Vue的异步组件和webpack的代码分析 (这种方式已经很古老了, 不用了嗷)
- const Home = resolve => { require.ensure([’…/components/Home.vue’], () => { resolve(require(’…/components/Home.vue’)) })};
- 方式二: AMD写法 (比方式一好一点)
- const Home = resolve => require([’…/components/Home.vue’], resolve);
- 方式三: 在ES6中, 我们有更加简单的写法来组织Vue异步组件和Webpack的代码分割 (代码简洁, 推荐)
- const Home = () => import("…/components/Home.vue")
const Home = () => import("../components/Home");
const About = () => import("../components/About");
const User = () => import("../components/User");
嵌套路由
- 嵌套路由是一个很常见的功能
- 比如在home页面中, 我们希望通过 /home/news 和 /home/message 访问一些内容
- 一个路径映射一个组件, 访问这两个路径也会分别渲染两个组件
- 实现嵌套路由有两个步骤
- 第一步: 创建对应的子组件, 并在路由映射中配置对应的子路由
- 注意!!! 嵌套的子路由path的路径前不能加 /
- 第二步: 在组件内部使用 router-view 标签
// 这里是创建的两个子组件
<template>
<div>
<ul>
<li v-for="item in news">{{ item }}li>
ul>
div>
template>
<script>
export default {
name: "HomeNews",
data() {
return {
news: ["新闻1", "新闻2", "新闻3", "新闻4"],
};
},
};
script>
<template>
<div>
<ul>
<li v-for="item in messages">{{ item }}li>
ul>
div>
template>
<script>
export default {
name: "HomeMessage",
data() {
return {
messages: ["消息1", "消息2", "消息3", "消息4"],
};
},
};
script>
const routes = [
{
path: "/home",
component: Home,
children : [
{
path : "",
redirect : "news"
},
{
path : "news",
component : HomeNews
},
{
path : "message",
component : HomeMessage
}
]
}
]
URL解析
- 一般来说一个URL长这样: scheme://host.domain:port/path?query#fragment
- scheme: 协议
- host: 主机
- domain: 域名
- port: 端口号
- path: 路径
- query: 查询
- fragment: 片段, hash
传递参数的方式
- 传递参数主要有两种类型: params 和 query
- params的类型
- 配置路由格式: /router/:id
- 传递的方式: 在path后面跟上对应的值
- 传递后形成的路径: /router/123, /router/abc
- 前面标题为 传递活动的路由的数据 有详细用法
- query的类型
- 配置路由格式: /router, 也就是普通配置
- 传递的方式: 对象中使用query的key作为传递方式
- 传递后形成的路径: /router?id=123, /router?id=abc
- 具体操作
- 第一步, 用v-bind为想要传递的router-link标签的 to 属性进行属性绑定, 值为一个对象, 对象中有两个属性, 第一个是path, 值为要传递的路由路径, 第二个为query, 值为一个对象, 里面为要传递的数据
- 第二步, 在对应的子组件中, 在组件构造器中computed中使用 $route.query 来接收传递过来的数据,
- 第三步, 处理数据, 使用数据
// 第一步 这里是根组件的代码
档案
<template>
<div>
<h2>我是Profile组件</h2>
<p>我是Profile里的信息</p>
<ul>
<li v-for="(item, key) in userData">{{ key }} : {{ item }}</li>
</ul>
</div>
</template>
<script>
export default {
name: "Profile",
computed: {
userData() {
return this.$route.query;
},
},
};
</script>
$route 和 $router 的区别
- $router本质是VueRouter实例, 就是一个对象包含所有的路由, 想要导航到不同的URL, 则使用 $router.push() 方法
- $route为当前活跃的路由组件对象
两个问题
- 为什么我们可以在各个组件中使用 $router 和 $route?
- 是因为所有组件都是来自, Vue类的原型prototype
- 而Vue类的原型prototype中就定义了, $router 和 r o u t e , 而 且 通 过 一 些 方 法 将 所 有 的 路 由 赋 值 给 了 route, 而且通过一些方法将所有的路由赋值给了 route,而且通过一些方法将所有的路由赋值给了router, 将当前活跃的路由组件赋值给了$route
- 所以我们可以在组件中使用这两个属性
- 为什么我们可以在组件模板中中使用 router-link 和 router-view 这两个标签
为什么要使用导航守卫
- 需求, 在一个SPA应用中, 如何修改网页的标题呢?
- 网页标题是通过 title 标签来现实的, 但是SPA只有一个固定的HTML, 切换不同的页面的时候, 标题并不会改变
- 但是我们可以通过JavaScript中 document.title来修改 title 中的内容 window.document.title = “新的标题” (window可以省略)
- 那么在Vue项目中, 在哪里修改? 什么时候修改比较合适呢?
- 普通的修改方式
- 很容易想到的修改标题的位置是每一个路由对应的组件.vue文件中
- 通过生命周期函数, 执行对应的代码来修改即可
- 但是当页面较多的时候, 这种方式不容易维护(因为要在多个页面执行类似的代码)
- 使用导航守卫
- 什么是导航守卫
- vue-router 提供的导航守卫主要用来监听路由的进入和离开的
- beforEach 守卫函数, 会在路由即将改变前触发, 这个守卫函数需要传入一个函数作为参数
- 这个传入的函数包含三个参数, to, from, next
- to : 即将要进入的目标的路由对象
- from : 当前导航即将要离开的路由对象
- next : 调用了该方法后, 才能进入下一个钩子, 前置守卫函数必须调用这个方法
- afterEach 守卫函数, 会在路由改变后触发, 这个守卫函数也需要传入一个函数作为参数
- 这个传入的函数包含两个参数, to , from
- 这两个参数的含义与beforEach中的函数的参数含义相同
- 后置守卫函数不需要调用next
// 普通的修改方式
// 这里是vue组件中的代码
<script>
export default {
name: "Home",
data() {
return {
message: "这里是首页",
};
},
created() {
document.title = "首页";
},
};
script>
router.beforeEach((to, from, next) => {
document.title = to.matched[0].meta.title;
next();
})
导航守卫补充
- 补充一: 上面已经提到了, 后置守卫函数afterEach 是不需要主动调用next()函数的
- 补充二: next()函数里面也是可以传入参数的, 具体可以浏览 vue-router 的官网
- 补充三: 上面所说的两个导航守卫都是全局守卫
keep-alive组件
- keep-alive是Vue内置的一个组件, 可以使被包含的组件保留状态, 或者避免重新渲染
- keep-alive组件有两个生命周期钩子函数
- activated(), 这个函数会在组件进入活跃的时候调用
- deactivated(), 这个函数会在组件离开活跃之前的时候调用
- 注意, 这两个函数只能在存在keep-alive组件的情况下使用
如何实现切换路由的时候保留组件状态
- 首先: 如果组件中具有组件嵌套, 而且有自动跳转默认组件的话, 需要在 路由配置 index.js文件中取消自动跳转
- 实现思路: 在组件中的data中存储离开路由前的路由路径, 在再次回到该路由的时候再将路由设置为离开路由前的路由路径达到保留组件状态的效果
- 具体操作
- 第一步: 在组件的data中存储默认的路由路径
- 第二步, 通过keep-alive中的activated() 函数, 在组件进入活跃的时候, 使用 $router.push() 方法, 将路由路径设置为默认路由路径
- 第三步, 通过导航守卫中的组件内守卫, beforeRouteLeave(to, from, next){} 将当前路由路径赋值给data中存储的默认路由路径
- 这样就可以达到切换路由的时候保留组件状态的效果
- 以后会有更好的方法达到这个效果
const routes = [
{
children : [
{
path : "news",
component : HomeNews
},
{
path : "message",
component : HomeMessage
}
]
}
]
<script>
export default {
name: "Home",
data() {
return {
message: "这里是首页",
path: "home/news",
};
},
activated() {
this.$router.push(this.path);
},
beforeRouteLeave(to, from, next) {
this.path = this.$route.path;
next();
},
};
script>
keep-alive中的两个重要属性
- keep-alive可以使被包含的组件保留状态, 或者避免重新渲染
- 但是我们有些组件就是要频繁的销毁和重新创建
- 这个时候就可以用到keep-alive标签中的两个重要的属性了
- include: 值为字符串或者正则表达式, 只有匹配的组件才会被缓存
- exclude: 值为字符串或者正则表达式, 匹配的组件都不会被缓存
- 注意, 有关正则表达式的代码都不能随意加括号
<keep-alive exclude="Profile,User">
<router-view>router-view>
keep-alive>