day-105-one-hundred-and-five-20230704–SPA单页面应用-vue-router-路由的配置
vue-router
常见面试题
- 面试题:说说你对 SPA 单页面的理解,它的优缺点分别是什么?
- 面试题:介绍一下路由的两种实现模式:hash模式 和 history模式
- 面试题:说一下路由跳转的方案及传参方式有哪些?
- 面试题:介绍一下 vue-router 中的导航守卫函数
- 面试题:介绍一下你对vue-router的理解?
代码正常但找不到html页面的问题
-
报错信息:
Conflict: Multiple assets emit different content to the same filename index.html
- 两种方案解决ERROR in Conflict: Multiple assets emit different content to the same filename index.html 的问题
- 原因可能是因为路径出错,导致webpack不能访问到对应的index.html文件。`
SPA单页面应用
-
SPA单页面应用与多页面应用
SPA
:single page application
-单页面应用。MPA
:多页面应用
。
-
面试题:说说你对 SPA 单页面的理解,它的优缺点分别是什么?
- 看图…
- 只要是由客户端渲染的内容,都是无法进行SEO优化的,如果需要做SEO优化,必须交由服务器进行渲染-
SSR-Server Side Render
- 传统技术:java(jsp)、php、python…
- 新的方案-前端方面的全栈工程师的方式:
- vue技术栈:node.js + Vue + nuxt.js
- React技术栈:node.js + React + next.js
前端路由
- 面试题:介绍一下路由的两种实现模式:hash模式 和 history模式
- 在SPA单页面应用中,主要是基于前端路由机制去管理的,无论使用何种路由模式,有一个重要的前提:路由切换,浏览器地址栏中的地址可以发生改变,但是不能让页面刷新!
- Hash哈希路由
-
原理:每一次路由切换,都是改变了页面的哈希值(但页面不会刷新)。vue-router内部会监测哈希值的改变-
事件:hashchange
,获取最新的哈希值;然后拿最新的哈希值
去路由表
中进行匹配
,把匹配的组件
,放在指定容器
中进行渲染
!之前渲染的组件
则被销毁
-彻底销毁,包含真实DOM
。<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>前端路由机制实现-Hash哈希路由</title> </head> <body> <nav class="nav-box"> <a href="#/">首页</a> <a href="#/product">产品中心</a> <a href="#/personal">个人中心</a> </nav> <div class="view-box"></div> <!-- IMPORT JS --> <script> // 取到路由容器。 const viewBox = document.querySelector(".view-box"); //首先要构建路由表-路由匹配规则:什么地址,就匹配什么组件或内容。 const routes = [ { path: "/", component: "首页的内容" }, { path: "/product", component: "产品中心的内容" }, { path: "/personal", component: "个人中心的内容" }, // { path: "/*", component: "404的内容" }, ]; //其次要进行路由匹配。 const matchHandle = function matchHandle() { //获取最新的哈希值。 let hash = location.hash; hash = hash.substr(1); // 去路由表中进行匹配。 let item = routes.find((item) => { return item.path === hash; }); //如果匹配不到,渲染404 if (!item) { viewBox.innerHTML = "404页面"; return; } //如果匹配到了,则把匹配的内容进行渲染! viewBox.innerHTML = item.component; }; // 监测哈希值的变化。 window.addEventListener("hashchange", matchHandle); //页面第一次渲染-也算一次路由切换,但要手动控制并手动进行触发。 if (!location.hash) { location.hash = "#/"; } matchHandle(); </script> </body> </html>
-
哈希路由模式是一种非常简单且方便操作的路由模式,只不过有人认为它比较丑,或者说不太那么真实-因为印象中
页面的切换
是地址的变化
,而不是单纯哈希值的变化
。
-
- History浏览器路由
-
原理:借助
html5即H5
中提供的HistoryAPI
,实现路由的切换,这样页面地址
会跟着改变
,但是不会让页面刷新
!在每一次切换后,获取最新的页面地址,去路由表
中进行匹配
,把匹配的内容
放在指定容器
中渲染
!而且会监测popstate事件,在触发回退或前进操作的时候,这个事件会触发,也进行路由的匹配。<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>前端路由机制实现</title> </head> <body> <nav class="nav-box"> <a href="/">首页</a> <a href="/product">产品中心</a> <a href="/personal">个人中心</a> </nav> <div class="view-box"></div> <!-- IMPORT JS --> <script> // 取到路由容器。 const viewBox = document.querySelector(".view-box"); const navBox = document.querySelector(".nav-box"); //首先要构建路由表-路由匹配规则:什么地址,就匹配什么组件或内容。 const routes = [ { path: "/", component: "首页的内容" }, { path: "/product", component: "产品中心的内容" }, { path: "/personal", component: "个人中心的内容" }, ]; //编写路由匹配规则。 const matchRoutes = function matchRoutes() { let pathname = location.pathname; let item = routes.find((item) => { return item.path === pathname; }); viewBox.innerHTML = item ? item.component : "404页面的内容"; }; //点击a标签,让其基于pushState进行跳转。 navBox.onclick = function (ev) { let target = ev.target; let targetTag = target.tagName; if (targetTag === "A") { //阻止默认行为。 ev.preventDefault(); history.pushState({}, "", target.href); matchRoutes(); } }; // 监听回退和前进。 window.onpopstate = matchRoutes; // 第一次渲染。 if (/\.html/.test(location.pathname)) { history.pushState({}, "", "/"); matchRoutes(); } //但手动刷新时,会发现地址不存在,但需要浏览器配合-当地址不存在时,要服务器把那个唯一存在的html返回给我们。 </script> </body> </html>
- HistoryAPI:
- pushState(状态对象,‘标题’,‘地址’) 新增历史记录池中当前的记录。
- replaceState(状态对象,‘标题’,‘地址’) 替换历史记录池中当前的记录,换成新跳转的地址。
- back() 回退一步。类似于 ==> go(-1)。
- forword() 前进一步。类似于 ==> go(1)。
- go() 回退或前进到那一步。
- HistoryAPI:
-
History路由,其路由跳转的地址,看上去真实很多-弥补了哈希路由的缺陷,但它存在一个更严重的问题:
- 真实页面只有一个,但是路由切换的地址有很多,这些地址是我们虚构的,并不真实存在。
- 基于pushState跳转的时候,页面不刷新,渲染的是真实页面,在真实页面中根据虚构的地址,匹配每个组件/内容,然后进行渲染即可!
- 但是如果页面在某个虚构的地址上刷新了,浏览器此时就会向服务器发送请求,服务器返回的是404-找不到资源。
- 所以History路由是需要服务器配合完成的:在客户端访问的页面地址不存在的情况下,不要返回404错误页面,而是返回唯一真实的页面即可!
-
- 在我之前的项目开发中,我一般都是基于哈希路由模式来管理前端路由机制的,主要原因是:不需要服务器配合,操作简便!
- Hash哈希路由
- 在SPA单页面应用中,主要是基于前端路由机制去管理的,无论使用何种路由模式,有一个重要的前提:路由切换,浏览器地址栏中的地址可以发生改变,但是不能让页面刷新!
Hash哈希路由机制原理
-
步骤:
- 取到
路由容器
-用于在路由变动时
修改该路由容器内部的内容
。 - 首先要构建
路由表
-路由匹配规则:什么地址
,就匹配对应组件或内容
。 - 其次要写一个
路由匹配函数
-用于根据当前最新的哈希值
匹配到路由表中的对应路由
,并把匹配到的路由
渲染到路由容器
上。 - 监测
哈希值的变化
,让便让其在路由变动时
执行路由匹配函数
-通过hashchange监听
,只能监听到页面渲染后
路由哈希值的变化
。- 但不能监听到
页面第一次向服务器请求到html文件时
的页面路由
。
- 但不能监听到
页面第一次渲染
-也算一次路由切换
,但要手动控制
并手动执行路由匹配函数
。
- 取到
-
代码示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>前端路由机制实现-Hash哈希路由</title> </head> <body> <nav class="nav-box"> <a href="#/">首页</a> <a href="#/product">产品中心</a> <a href="#/personal">个人中心</a> </nav> <div class="view-box"></div> <!-- IMPORT JS --> <script> // 取到路由容器。 const viewBox = document.querySelector(".view-box"); //首先要构建路由表-路由匹配规则:什么地址,就匹配什么组件或内容。 const routes = [ { path: "/", component: "首页的内容" }, { path: "/product", component: "产品中心的内容" }, { path: "/personal", component: "个人中心的内容" }, // { path: "/*", component: "404的内容" }, ]; //其次要进行路由匹配。 const matchHandle = function matchHandle() { //获取最新的哈希值。 let hash = location.hash; hash = hash.substr(1); // 去路由表中进行匹配。 let item = routes.find((item) => { return item.path === hash; }); //如果匹配不到,渲染404 if (!item) { viewBox.innerHTML = "404页面"; return; } //如果匹配到了,则把匹配的内容进行渲染! viewBox.innerHTML = item.component; }; // 监测哈希值的变化。 window.addEventListener("hashchange", matchHandle); //页面第一次渲染-也算一次路由切换,但要手动控制并手动进行触发。 if (!location.hash) { location.hash = "#/"; } matchHandle(); </script> </body> </html>
History浏览器路由机制原理
-
步骤:
- 取到
路由容器
-用于在路由变动时
修改该路由容器内部的内容
。 - 首先要构建
路由表
-路由匹配规则:什么地址
,就匹配对应组件或内容
。 - 其次要写一个
路由匹配函数
-用于根据当前最新的location.pathname
匹配到路由表中的对应路由
,并把匹配到的路由
渲染到路由容器
上。 - 在路由需要变动时,基于
history.pushState()
进行跳转,同时在跳转时,手动执行路由匹配函数
。 - 监测
同一文档的两个历史记录条目之间导航变化
,让其在鼠标点击前进后退按钮或执行history.go()
这一类函数时自动触发路由匹配函数
。- 但不能监听到
history.pushState()
进行跳转的情况。
- 但不能监听到
页面第一次渲染
-也算一次路由切换
,但要手动控制
并手动执行路由匹配函数
。
- 取到
-
代码示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>前端路由机制实现</title> </head> <body> <nav class="nav-box"> <a href="/">首页</a> <a href="/product">产品中心</a> <a href="/personal">个人中心</a> </nav> <div class="view-box"></div> <!-- IMPORT JS --> <script> // 取到路由容器。 const viewBox = document.querySelector(".view-box"); const navBox = document.querySelector(".nav-box"); //首先要构建路由表-路由匹配规则:什么地址,就匹配什么组件或内容。 const routes = [ { path: "/", component: "首页的内容" }, { path: "/product", component: "产品中心的内容" }, { path: "/personal", component: "个人中心的内容" }, ]; //编写路由匹配规则。 const matchRoutes = function matchRoutes() { let pathname = location.pathname; let item = routes.find((item) => { return item.path === pathname; }); viewBox.innerHTML = item ? item.component : "404页面的内容"; }; //点击a标签,让其基于pushState进行跳转。 navBox.onclick = function (ev) { let target = ev.target; let targetTag = target.tagName; if (targetTag === "A") { //阻止默认行为。 ev.preventDefault(); history.pushState({}, "", target.href); matchRoutes(); } }; // 监听回退和前进。 window.onpopstate = matchRoutes; // 第一次渲染。 if (/\.html/.test(location.pathname)) { history.pushState({}, "", "/"); matchRoutes(); } //但手动刷新时,会发现地址不存在,但需要浏览器配合-当地址不存在时,要服务器把那个唯一存在的html返回给我们。 </script> </body> </html>
路由的初始化
路由的配置
- 补看第38个视频。
- 先在页面中使用路由容器,把路由匹配的组件进行渲染。
- 在
fang/f20230704/day0730_router/node_modules/vue-router/dist/vue-router.js
中:- 返回一个VueRouter类,而VueRouter类中有一个install静态函数,用于在Vue.use()调用install静态函数。
- VueRouter类,如果什么都不传,就当传入一个对象。如果不传mode,就是传一个哈希模式。而mode也可以看到可以传入三个选项。
- 创建router-编写路由表。
- new VueRouter():可以在内容设置一些属性。里面有mode、base、routes等,可以设置路由的全局状态。而最重要的是routes,可以设置路由表。
- 配置好vue路由表的各项。
- 在根组件初始化时中设置routes这个属性。
- 在Vue.use()时,通过调用了全局minix设置一些生命周期。
- 在页面第一次渲染或者路由切换的时候:
- 先到路由中进行匹配,找到匹配的组件。
- 把匹配的组件放在路由容器中渲染。
划分路由
- 看结构及样式及功能是否一样,如果一样,就不划分路由,直接用选项卡。
- 目前划分路由的方式:
- 除一级路由外,主页上的路由全部为二级路由:
- 一级路由
/login
-->Login登录页
/
-->Index主页
/home
-->首页
-Index主页下的二级路由
/template
-->通用物模型
-Index主页下的二级路由
/user/manage
-->用户管理
-Index主页下的二级路由
/user/info
-->用户信息
-Index主页下的二级路由
- …
至于路由分类,则用parent父类来做
-根据该字段构建出路由树
。
*
-->404
- 一级路由
- 整个页面都不同的地方,为一级路由。主页上的路由根据导航栏的展开收缩等依类别分为二级路由及三级路由及多级:
- 一级路由
/login
-->Login登录页
/
-->Index主页
/home
-->首页
-Index主页下的二级路由
/template
-->通用物模型
-Index主页下的二级路由
/user
-->用户相关的页面
-Index主页下的二级路由
,这个页面也可能并不实际存在,只是单纯的展开及做分类的标识。/user/manage
-->用户管理
-user下的三级路由
/user/info
-->用户信息
-user下的三级路由
- …
至于路由分类,则用parent父类来做
-根据该字段构建出路由树
。
*
-->404
- 一级路由
- 除一级路由外,主页上的路由全部为二级路由:
配置多级路由
- 在某级的路由组件中,用
<router-view/>
做一个路由视图,以便让其子级路由渲染在那里。 - 在路由表中,在对应路由配置对象中的children属性上设置一个子级路由表。
- 为了原路由地址中渲染出来的视图中,要对应路由配置对象中的redirect属性上设置一个对应的子级路由地址。
路由的懒加载
如果构建路由表的时候,像这样处理:把所有组件都基于 ES6Module 规范,事先导入进来处理,这样最后打包的时候,会把所有组件都打包到 app…js 中「主JS中」
- 主JS会很大很大
- 页面第一次渲染,就需要从服务器获取这个主JS,需要很久的时间,再没有获取带期间,页面是白屏效果
这样很不好!我们期望:
- 只把最开始就要加载的组件(比如首页)打包到主JS中,让文件保持最小的状态,加快页面第一次渲染的速度
- 其余的组件,都分类打包到单独的JS中
- 可以每个组件打包为一个JS
- 也可以某个版块下的所有组件打包成为同一个JS
- 还可以自己规定谁和谁打包在一起
- 最开始加载页面的时候,这些JS并不会被加载,只有路由切换,需要渲染对应组件的时候,再从服务器获取相应的JS,实现动态加载渲染!!
- 而且只要加载过一次对应的JS,下一次再切换到这个组件,就不需要重新加载了!!
===================路由懒加载「前端必做性能优化」
@1 webpack打包的时候,需要分割打包
@2 实现按需导入、加载/渲染
借助:webpack(webpackChunkName) + ES6内置API:import
路由跳转
- 面试题:说一下路由跳转的方案及传参方式有哪些?
- 实现路由跳转有两种方案:
- 基于
<router-link>
实现跳转。-
渲染后的结果是a标签:
- 但是
<router-link>
可以自动识别路由的模式,控制a标签的href值。 - 而且还会和当前的路由地址进行匹配,把相匹配的导航设置router-link-active/router-link-exact-active选中的样式类!
- 最后
<router-link>
的语法更强大,不仅指定了跳转的地址,还有相应的传参信息!
- 但是
-
<router-link to="/personal">
以push方式-新增历史记录,跳转到/personal路由。 -
<router-link to="/personal" replace>
设置replace属性,则不会新增历史记录,而是替换现在的历史记录。 -
to属性的值可以是一个对象:
:to="{ path:'/personal', query:{ ...// } }"
:to="{ name:'/personal_profile', params:{ ...// } }"
-
在
<router-link>
上设置exact属性后,当前<router-link>
只能进行精准匹配,一般只用于地址是/
的情况!
-
- 基于
编程式导航
-也就是this.$router中提供的方法,实现跳转。
- 基于
- 实现路由跳转有两种方案:
- 而且这两种方案中,既支持按照路径跳转
路由匹配
在vue-router中的路由匹配规则
- 普通匹配 router-link-active
- 精准匹配 router-link-exact-active
当前路由地址 | 需要匹配的地址 | 普通匹配 | 精准匹配 |
---|---|---|---|
/product | / | 可以 | 不可以 |
/product | /product | 可以 | 可以 |
/product | /personal | 不可以 | 不可以 |
/product2 | /product | 不可以 | 不可以 |
/personal/order | /personal | 可以 | 不可以 |
- …
后台管理系统颜色
- 如果后台管理系统,用的是element-ui框架,设计师没给色彩,直接用element-ui的主题色。
路由跳转
this. r o u t e r 与 t h i s . router与this. router与this.route
- this.$router拿到的new VueRouter()创建出来的实例。它的原型是VueRouter.prototype。
- 跳转相关的方法:
- back
- forward
- go
- push 和
<router-link>中to属性的语法
一致。 - replace
- 在路由跳转过程中,我们可以传递一些信息,总结下来有三种传参方案:
- 问号传参-适用于路径/名字跳转。
- 优势:
- 传参的信息在地址栏中存在,即便在目标页面刷新,传递的信息依然可以解析出来;
- 支持路径和名字跳转。
- 弊端:传递的信息因为暴露在地址栏中,所以不安全。并且有长度限制;而且传递的值最后都被转为字符串;有人认为比较丑。
- 优势:
- 路径参数 只适用于路径的跳转。
- 把传递的信息,作为路由地址规则中的一部分。所以想基于这个方案传递参数,需要先改变路由匹配的规则。
- 隐式传参 只适用于名字跳转。
- 优势:可以传递任何类型的值;传递的信息不会暴露在外面,所以安全且没有长度限制。
- 弊端:只适用于名字跳转。因为传递的信息没有在地址栏中,所以在目标页面刷新,刚才传递的信息就丢失了!
- 问号传参-适用于路径/名字跳转。
- 跳转相关的方法:
- this.$route当前路由匹配到的信息。
对象与urlencoded
-
安装
axios
后,会自带一个qs模块
。这个qs模块
主要是实现urlencoded格式字符串
和对象
之间的转换的!npm install axios --save
//安装 axios 后,自带 qs 模块,这个模块主要是实现 urlencoded 格式字符串 和 对象 之间的转换的! import qs from 'qs'//安装axios后,会自带一个qs模块。 //urlencoded:lx=wx&name=zhufeng let obj = {lx:'wx',name:'zhufeng'} console.log(`qs.stringify(obj)-->`, qs.stringify(obj));//lx=vw&name=xhufeng console.log(`qs.parse('lx=vw&name=xhufeng')-->`, qs.parse('lx=vw&name=xhufeng'));//{lx: 'vw', name: 'xhufeng'}