| ✍️ Tangxt | ⏳ 2020-07-25 | 🏷️ vue-router |
路由器
路由是什么
搞清楚这几个概念:
hash 模式?history 模式?memory 模式?
正则表达式的使用
VueRouter 的一些 API(下节课讲)

理解「路由」,从生活概念入手
我们日常生活中接触到的路由有很多,如「路由器」,不过,现在似乎都用光猫了!
猫和路由器的区别:Easy-Key——科普:猫、路由器和交换机的区别和联系 - 知乎
家里有一个或一个以上的人要上网就得用到路由器了!

分发:只要满足一对多的情况,就叫做分发,即分别发送到各个地方
路由器的主要作用是分发请求的,只要一个东西分发了请求,那么它就是路由,而这个东西就是「路由器」
示例:发送一个请求抖音的信号,路由器就会分发出去……
话说,如果面试官问你「什么是路由?」,你会怎么回答?
分发「请求」的东西或者分发「请求」的对象是「路由器」(器就是器物之意),而分发「请求」就是「路由」
然而,面试官问的是「前端路由」,而我们回答的是「上网路由」,所以我们要对我们了解到的「上网路由」细化成「前端路由」
方方翻译了一篇文章——停止学习框架
评论中有一些很典型的思维:
不学框架?谁会要你?现在的前端不就是学框架的吗?为啥要停止学习框架?我得用 100% 的时间来学习框架才行……
方方是如何学习 router 的?
route 是「路线」, router 是路由器,即路由器对象……
codesanbox 创建一个项目时,可以通过点击「有颜色的 box 」来创建 -> 原生 JS(logo 黄色)、React(logo 蓝色)、Vue(logo 绿色)
需求:


代码写得不好看……
hash(#1)即页面一打开
number默认就是1->number = number || 1->url后缀没有#xx,div#app里边显示的就是默认路由过来的界面!

保证用户总是能够看到一个东西,不能出现意外的情况

需求:

代码有点麻烦,下个 ★ 会讲到!
x (想不出名字,就用这个 x ,而 x 的意思是表示函数代码写完之后一定会修改的) -> 看到函数体的逻辑——根据你想去的地方,展示你的界面,即从一个源地址,到达目的地,而这就是路由 -> 所以函数的名字是「 route 」<a href="#1">go to 1</a>
<a href="#2">go to 2</a>
<a href="#3">go to 3</a>
<a href="#4">go to 4</a>
<div id="app"></div>
<div id="div1" style="display: none;">1</div>
<div id="div2" style="display: none;">2</div>
<div id="div3" style="display: none;">3</div>
<div id="div4" style="display: none;">4</div>
<div id="div404" style="display: none;">你要找的内容被狗吃了</div>
function route() {
// 获取 hash
let number = window.location.hash.substr(1)
// 找到坑位
let app = document.querySelector('#app')
// 默认路由
number = number || 1
// 重置界面
if (app.children.length > 0) {
// 旧界面隐藏
app.children[0].style.display = "none"
// 旧界面回到原处
document.body.appendChild(app.children[0])
}
// 获取界面
let div = document.querySelector( `#div${number}` )
if (!div) {
div = document.querySelector( `#div404` )
}
div.style.display = "block"
// 展示界面 or 新界面入坑
app.appendChild(div)
}
route()
window.addEventListener('hashchange', () => {
console.log('hash 变了')
route()
})
接下来看看「路由表」
route 函数里边,凭啥就只有 1/2/3/4 这 4 个 hash?难道就不能自定义吗?凭啥 1 就是 div1 呢?我 one 是 div1 不行么?
目前
route函数里的规则是很奇怪的,即只能根据#div${number}这样的规则来获取相应的界面,如#1 -> div1,为啥就不能是#1 -> div666呢?凭啥只能是div1这个界面?我使用你这个route函数,凭啥必须要我在页面中写个叫#div1的div?
div-> 界面在内存中创建
const div1 = document.createElement('div')
div1.innerHTML = '1'
const div2 = document.createElement('div')
div2.innerHTML = '2'
const div3 = document.createElement('div')
div3.innerHTML = '3'
const div4 = document.createElement('div')
div4.innerHTML = '4'
const routeTable = {
'1': div1,
'2': div2,
'3': div3,
'4': div4
}
// route 里边:
// 获取界面 -> number -> string,即便 number 不是 string 也会自动转成 string
let div = routeTable[number]
其实,路由是很简单的,只要你把基础的东西理解透了,就不用管是 Vue 、React 、Angular,因为它们的实现思路肯定是一样的,只是提供的 API 不一样罢了!
需求:

简单来说:
1/1 -> 去 1.1 这个子界面在路由里边再分一个路由,而这个路由就是「子路由」或「嵌套路由」
路由即是路线
怎么做?
route参数是container-> 本来叫 root,由于函数名 route 与 root 发音过于相似就 GG 掉了……
思路:

我看了一下 Vue Router 的嵌套路由概念:
发现根路由的视图,与子路由的视图一起的,就像是这样:
/user/foo/profile /user/foo/posts
+------------------+ +-----------------+
| User | | User |
| +--------------+ | | +-------------+ |
| | Profile | | +------------> | | Posts | |
| | | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------+
有点像是一个 div 嵌套着一个 div -> 我之前以为是这样的:

那个
true标识的才是大家认为的嵌套路由的结果……而不是那个false(我原先以为需求是类似false这样的)
方方是这样实现的:
const div11 = document.createElement('div')
div1.innerHTML = '1.1'
const div12 = document.createElement('div')
div2.innerHTML = '1.2'
const div13 = document.createElement('div')
div3.innerHTML = '1.3'
// 再定义一个子路由表
const route1Table = {
"1/1": div11,
"1/2": div12,
"1/3": div13
}
// container -> root -> 如果是 #1/2,那么 1 就是 root or container
function route(container) {
// ……
}
至此,关于路由几个重要的概念,基本上已经讲清楚了,接下来,就来看看 hash 、 history 、 memory 的区别
之前讲了路由是什么,以及路由相关的概念
我们知道hash在任何情况下都能做前端路由……
但这种姿势,对 SEO 不友好,也就是说「服务器收不到 hash,内容是不会被搜索引擎收录的」

不过,这也不是绝对的,google 做了处理,可以对 hash 做个 SEO 处理,当然,这得要你对服务器做一些配置
做法:
hashbang -> #!xxx -> 带!就行了…… -> 不推荐这样做……
我觉得不用
hash模式是因为#太丑了! -> 用history模式的话,可以直接/1这样获取界面……而不是#1这样
用户随意输入一个 url,响应回来的是一个有用的页面(如首页),而不是 404 页面(404 页面是无用的)!
所以history模式是:
后端将所有前端路由都渲染到同一页面!
缺点:IE8 及以下支持 -> 可忽略不计!
key变成/1这样了a链接的默认行为 -> 使用for……of迭代所有aDOM 对象historyAPI(history.pushState(obj,'page 2','xx.html')) -> 在不刷新页面的情况下,更改 URLpath的改变,更改界面 -> 然而没有相应的事件监听方法 -> 定义一个函数,点击a就执行callbackhistory模式为啥要让所有的页面指向同一个页面呢?
因为用户喜欢刷新,不然
/66这样存在的路径就是 404 页面了!
感觉之所以用
history模式是因为要fuck掉 404 页面!而且也有 SEO!因为请求服务器就是带/1的!还有就是用户不需要刷新了……
注意,后端是需要配置的:

Q:`for……of`?
在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句
简单来说,索引有0、1、2……的即可迭代,而像xxx这样的对象属性是无法迭代的,而且也不会迭代继承的……即不会迭代原型链上的……
Q:证明 JS 生效?
对 JS 文件 console.log('fuck')
Q:`http://www.baidu.com`?
表面上看是这样,其实发的请求是http://www.baidu.com/这样的!
Q:如何让对象的 key 值是变量?
姿势一:
const username = "name";
const obj = {
[username]:"king123"
}
console.log(obj.name); //output-> king123
姿势二:
const username = "name";
const obj = {};
obj[username] = "king123";
console.log(obj.name); //output-> king123
console.log(obj[username]) // output -> king123
所以可有:
const baseUrl = '/router/'
let hashTable = {
'1': div1,
'2': div2,
'3': div3,
'4': div4
}
function createRouteTable(baseUrl, hashTable) {
let obj = {}
for (let key in hashTable) {
obj[baseUrl + key] = hashTable[key]
}
return obj
}
const routeTable = createRouteTable(baseUrl, hashTable)
console.log(routeTable)
结果很奇怪:

似乎是那个 value 值的效果! -> 即那个
divDOM 对象
➹:How to use variable as an Object key in JavaScript - Reactgo
hash模式和history模式对比history模式:点击a链接 -> 阻止默认行为,不刷新页面 -> 改路径 -> 路由一下(默认访问index.html,或者说是root路由,那么默认返回的是/1对应的界面)hash模式:在任何情况下都能做前端路由,但就是对 SEO 不友好! -> 不友好原因是服务器收不到这个#xxx使用history必须满足两个条件:
/1、/2、/3等指向的都是同一个index.html页面 -> 注意这不是 404 页面哈!话说,memory模式是什么呢?
既不用
hash存储路由,也不用history存储路由,而是用一个对象来存储,如localStorage
hash和history的前端路由是作用于url那个输入地址栏的,而memory则是把前端路由弄在localStorage里边的,这样一来,页面总是那个url地址!
number的值是从localStorage里获取的代码大概是这样的:
// ……
let number = window.localStorage.getItem('xxx');
// ……
window.localStorage.setItem('xxx', `${baseUrl}${href}`)
注意,以后写
baseUrl的时候,请不要这样'/router/'这样写,而是/router,不然,很容易出现/router//1这样的情况……
这种模式适合非浏览器,如你的 APP 想做路由,而 APP 的第一页是没有路径的!
使用场景:
一般,我们前端都用不上这种模式……
url上边 -> 可分享的,url 记录了此刻的页面信息路径放到url里边,那么就是memory模式,一般用在浏览器是localStorage,而 APP 则是放在本地数据库里边! -> 缺点:它是单机的,即你分享页面给人家是默认的页面把之前学到的核心概念 -> 对应到 Vue Router



我们使用 vue-router,那么就不需要手动去写上边这三种路由模式了,直接通过mode选项设置就好了!
而这就是所谓的封装了,即我们不用去管router对象是如何拿到路径等这些细节,反正就是能拿到!
之后,就是把router这个封装对象传给根组件#app去处理了!
通过this.$route来获取用户传的各种路径信息,如his.$route.params.username,具体细节,看文档就好了!

path: '/user/:id'的实现原理:
用正则表达式做的!把
:id的值封装成一个对象,然后传给User组件

一个路由(
/user/1):一个容器,嵌套路由(/user/1/profile):一个容器里边再放一个容器
不用操作 DOM 的 JS,可以放到前边,即
head标签里边
把 起步代码 CRM 一下 -> 得到一些形象:
默认是hash模式

似乎就是把to="/1"的/1与#这个 hash 符给拼接了 -> #/1
也就是说,vue-router 并咩有 window.location.hash.substr(1)里边的substr这一步咯!
改成是history模式:
const router = new VueRouter({
mode: 'history',
routes: [...]
})

memory模式就不测试了,一般我们前端是用不上的!
得到的知识点:
router-link:切换路由,如/1、/2router-view:展示界面,就是个container,不过会被对应的组件template给替换掉!routes -> 路由表(数组,元素是对象)router -> 对路由操作的封装(一个VueRouter实例对象) -> 开发者写上一些配置选项,如mode:'history',那么router就会知道如何来处理路由了! -> 依旧是Options APIapp(new Vue()) -> 把router作为一个Option传入,即new Vue({router})

照理说,哪个行数最少就看哪个(min.js除外),但vue-router.js的是最通用的,所以就看这个了 -> 其实esm.js代码行数最少(ES6 模块规范)……
准备好文档材料之后,就开始看了(把代码下载下来,打开 VS Code,折叠起来看!)
从你使用最常用的代码开始看,如router-link & router-view:
看router-link:

Link 就是个 Vue 组件,名字叫RouterLink,它需要接收这么几个属性:

我们写了这个router-link标签,那么渲染出来的东西是什么呢?
h(this.tag, data, this.$slots.default)
我们要重点关注的是data(给元素标签添加属性):
onclick事件,就跟我们之前原生写history模式路由一样,即遍历所有的a有.link标签,为它们绑定click事件,阻止它们的默认跳转发请求行为绑定的click事件会调用handler方法:

简单来说,我们点击a标签就会调用push方法! -> 类似pushState()这样的…… -> 不同的mode有不同的push方法
看源码要有目的的去看……如我们要看
router到底是个什么东东 ->this.$router->this.$options.router-> 就是我们new Vue({ router })时所传的router这个封装对象
小结:
router-link被click了 -> 执行router.push or router.replace -> 拿push来说 -> VueRouter.prototype.push -> 执行this.history.push -> history有三种:HTML5History.prototype.push、HashHistory.prototype.push、AbstractHistory.prototype.push(也就是memory) -> 它们的共同点,都会调用transitionTo,其中首先会更新路由(updateRoute(route)),然后调用 callback(onComplete(route))Q:`h`的含义?
h是一个 Vue 提供的你想怎么创建就怎么创建的 API -> 让你用 JS 的形式来创建任意的标签,而不是用template的v-if来搞!
如:
<ah :level="1">Hello!</ah>
template 形式:
<h1 v-if="level === 1">
<slot></slot>
</h1>
<h2 v-else-if="level === 2">
<slot></slot>
</h2>
h形式:
function render (createElement) {
return createElement(
'h' + this.level, // 标签名称
this.$slots.default // 子节点数组
)
}
createElement的重命名就是h->h就是用于创建元素的函数!
h代表的是 hyperscript:表示的是 “生成 HTML 结构的脚本”,缩写为h是因为它更容易去输入,可认为是createElement的缩写
看router-view:
怎么看思路同
router-link一样,想看它是什么,然后有什么用
它是个View:

render的结果是:
h(component, data, children)
它没有事件监听,直接渲染一个组件,而这个组件怎么来的呢? -> 显然是,当前路由对应哪个组件,那就渲染哪个组件 -> 从缓存里拿!
path,一个path对应着一个界面hash模式、history模式、memory模式 -> 这些实现可以看人家的,一般都会有人这么做的 -> 了解有关路由的重要概念 -> CRM 大法使用vue-router -> 看源码(找常用的组件标签来看,如route-link、router-view)VueRouter.prototype.match = function match() {},注册组件时是Vue.component('RouterView', View);、定义组件时的name属性是 name: 'RouterView'、在template使用组件时用的是router-view -> 看源码就像是在看这东西做了什么,是如何做的,也可以说是在看一篇优秀的作文是怎么写的 -> 对于我们而言,就是模仿它的写法,弄懂我们这样使用这个东西,为啥会有这样效果的原因,如为啥点击一个a标签,页面就切换了一个界面……router.push() -> 声明式导航(<router-link :to="...">)this.$route指的是当前路由对象,path/meta/query/params小白回答:hash 模式 url 带
#号,history 模式不带#号
这个回答其实和没有回答是一样,百度一下都知道了,官网文档也有,如果这样回答就能通过,那么那个面试官问这个问题又有什么意义呢?其实这个问题的意义是考验你的开发经验,与实际场景的应用和与后端人员的配合。
大牛解答:hash 模式 url 里面永远带着
#号,我们在开发当中默认使用这个模式。那么什么时候要用 history 模式呢?如果用户考虑 url 的规范那么就需要使用 history 模式,因为 history 模式没有#号,是个正常的 url 适合推广宣传。当然其功能也有区别,比如我们在开发 app 的时候有分享页面,那么这个分享出去的页面就是用 vue 或是 react 做的,咱们把这个页面分享到第三方的 app 里,有的 app 里面 url 是不允许带有#号的,所以要将#号去除那么就要使用 history 模式,但是使用 history 模式还有一个问题就是,在访问二级页面的时候,做刷新操作,会出现 404 错误,那么就需要和后端人配合让他配置一下 apache 或是 nginx 的 url 重定向,重定向到你的首页路由上就 ok 啦。
以上摘自:VUE 路由的 hash 模式与 history 模式的区别?
官方介绍:HTML5 History 模式
关于 Vue 的路由一直以来个人都觉得是一件很神奇的事情,一个单页面应用居然可以做到多路由跳转并按需加载页面代码。以往的做法都是通过锚点来定位对应的页面代码,而这种古老的操作方式最大的问题就是首屏加载缓慢,一次性加载了所有页面代码。
那么 Vue-router 又是怎么实现的呢?
首先,这个 router 有两种模式:hash 模式(默认)、history 模式(需配置mode: 'history')
hash 与 history 的区别:
| hash | history | |
|---|---|---|
| url 显示 | 有#,很 Low | 无#,好看 |
| 回车刷新 | 可以加载到 hash 值对应页面 | 一般就是 404 掉了 |
| 支持版本 | 支持低版本浏览器和 IE 浏览器 | HTML5 新推出的 API |
然后,我们来研究下两者的原理:
我们先来认识下这位朋友#,这个#就是** hash 符号**,中文名哈希符或锚点,当然这在我们前端领域姑且这么称呼。
然后哈希符后面的值,我们称之为哈希值。OK,接下来我们继续分析它的原理。路由的哈希模式其实是利用了window可以监听onhashchange事件,也就是说你的 url 中的哈希值(#后面的值)如果有变化,前端是可以做到监听并做一些响应(搞点事情),这么一来,即使前端并没有发起 http 请求它也能够找到对应页面的代码块进行按需加载。
后来人们给他起了一个霸气的名字叫前端路由,成为了单页应用标配。
大伙可以围观下网易云音乐的 url 模式:https://music.163.com/#/friend
我们先介绍一下 H5 新推出的两个神器:pushState与replaceState
具体自行百度,简而言之,这两个神器的作用就是可以将 url 替换并且不刷新页面,好比挂羊头卖狗肉,http 并没有去请求服务器该路径下的资源,一旦刷新就会暴露这个实际不存在的“羊头”,显示 404。
那么如何去解决 history 模式下刷新报 404 的弊端呢,这就需要服务器端做点手脚,将不存在的路径请求重定向到入口文件(index.html),前后端联手,齐心协力做好“挂羊头卖狗肉”的完美特效。
至此,我们的前端路由在实现与展示效果上又更进了一步!
总之,
pushState方法不会触发页面刷新,只是导致 history 对象发生变化,地址栏会有反应。
传统的路由指的是:当用户访问一个 url 时,对应的服务器会接收这个请求,然后解析 url 中的路径,从而执行对应的处理逻辑。这样就完成了一次路由分发。
而前端路由是不涉及服务器的,是前端利用hash或者 HTML5 的history API 来实现的,一般用于不同内容的展示和切换
history模式下,build之后本地 index.html 打开是无效的。
hash模式下,build之后本地 index.html 打开正常!
➹:【前端路由】Vue-router 中 hash 模式和 history 模式的区别
➹:VUE 路由的 hash 模式与 history 模式的区别?-程序思维
HTML5 新接口,可以改变网址(存在跨域限制)而不刷新页面,这个强大的特性后来用到了单页面应用如:vue-router,react-router-dom中。
注意:仅改变网址,网页不会真的跳转,也不会获取到新的内容,本质上网页还停留在原页面!
window.history.pushState(data, title, targetURL);
@状态对象:传给目标路由的信息,可为空
@页面标题:目前所有浏览器都不支持,填空字符串即可
@可选 url:目标 url,不会检查 url 是否存在,且不能跨域。如不传该项,即给当前 url 添加 data
window.history.replaceState(data, title, targetURL);
@类似于 pushState, 但是会直接替换掉当前 url, 而不会在 history 中留下记录
popstate事件会在点击后退、前进按钮(或调用history.back()、history.forward()、history.go()方法)时触发。前提是不能真的发生了页面跳转,而是在由history.pushState()或者history.replaceState()形成的历史节点中前进后退
注意:用history.pushState()或者history.replaceState()不会触发popstate事件。
window.onpopstate = function(event) {
console.log(event.state);
console.log(window.history.state;);
};
以上两种方式皆可获取之前在pushState和replaceState中传入的data