0%

初始化

下载 android Studio(4.1.1), JDK8,
安装完成后,导入 demo 项目(300M,).
开始初始化下载,有可能下载较慢,科学网即可.
完成后,项目同步有可能报错(Sync Project with Gradle Files).需要下载对应 SDK 版本.
sdk 报错解决方法

打包

build => Genenate Signed Budle/APK
WechatIMG1989.png
WechatIMG1990.png
WechatIMG1992.png
WechatIMG1994.png

打包完成

WechatIMG2057.png

H5 端 webview

修改浏览器的固定 url

将 webview 用 HBuilderX 打开,修改pages/index/webview.vue中的 url 地址.

打包成 wgt 包

webview.zip解压.用 HBuilderX 修改后发行为原生 app,制作应用 wgt 包.生产包后修改后缀为 zip 解压.
1.png
WechatIMG2086.png
将解压后文件复制到simpleDemo/src/main/assets/apps/__UNI__0DD8DA0/www
重新打包.

logo 及启动图制作

logo 生成
启动图生成
WechatIMG2093.png
生成后复制到/simpleDemo/src/main/res/drawable.

图片的相对路径

图片的前缀不再使用固定的 url,可以加/给图片地址.就会自动在前面拼接上 url 的 origin 地址前缀.

1
2
3
4
5
//例如
const baseURL = "/";
return baseURL + item.downloadPath;
//例如
"/Rest/pc/file/download?" + item.url;

如果存在网关,则需要加上网关.

export 和 export default 区别

  • 在一个文件或模块中,export、import 可以有多个,export default 仅有一个
  • 通过 export 方式导出,在导入时要加{ },export default 则不需要
1
2
3
4
5
6
7
//a.js
let sex = "boy";
export default sex(sex不能加大括号)
//原本直接export sex外部是无法识别的,加上default就可以了.
//但是一个文件内最多只能有一个export default。
//其实此处相当于为sex变量值"boy"起了一个系统默认的变量名default,
//自然default只能有一个值,所以一个文件内不能有多个export default。
1
2
3
4
5
6
// b.js
本质上,a.js文件的export default输出一个叫做default的变量,
//然后系统允许你为它取任意名字。所以可以为import的模块起任何变量名,且不需要用大括号包含
import any from "./a.js"
import any12 from "./a.js"
console.log(any,any12) // boy,boy

defer 和 async 区别

区别主要在于一个执行时间,script 的执行时机.
两个网络请求都是异步的,不会阻塞浏览器解析 HTML.加载是都会加载,执行就有区别了.

defer会在 dom 解析完之后执行,并且多个defer会按照顺序执行,
async则是在 js 加载好之后就会执行脚本,并且多个async,哪个加载好就执行哪个

解析:
在没有 defer 或者 async 的情况下:会立即执行脚本,所以通常建议把 script 放在 body 最后
<script src="script.js"></script>
async:有 async 的话,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。
但是多个 js 文件的加载顺序不会按照书写顺序进行
<script async src="script.js"></script>
defer:有 defer 的话,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成,并且多个 defer 会按照顺序进行加载。
<script defer src="script.js"></script>

link 和 @import 区别

作用: 样式的导入方式

  1. 引入内容不同.link除了引入样式还可以引入图片等文件,@import只能引入 css.
  2. 加载顺序不同.link在页面加载时同时加载,@import需要等页面加载完.
  3. 兼容性不同.linkXHTML标签,无兼容性问题.@import低版本浏览器有可能不支持.
  4. 对 JS 支持不同.link支持用 js 控制 DOM 去改变样式.@import不支持.

为什么 link 用 href 获取资源,而 script 和 img 用 src?

link 用于在当前文档和引用资源建立联系,
src 用于替换当前元素.

1
<link href="common.css" rel="stylesheet" />

在文档中添加 link 标签,浏览器会识别该文档为 css 文件,就会并行下载资源并且不会停止对当前文档的处理。这也是为什么建议使用 link 方式来加载 css,而不是使用@import 方式

1
<script src="a.js"></script>

当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架 等元素也如此,类似于将所指向资源嵌入当前标签内。这也是为什么将 js 脚本放在底部而不是头部

图片懒加载和预加载

懒加载: 延迟加载图片,达到加载条件时再加载。
原理:img 的 src 属性初始不放置实际需要的链接,而是存在 data-url 属性中,达到某条件后再动态充值 src 属性值为 data-url 里的链接地址,实现懒加载。
作用:减轻服务器压力,节约流量,页面加载速度快。
预加载:提前加载图片,当用户需要查看时是直接从浏览器缓存里取的资源,用户操作体验速度快。
原理:在浏览器加载页面时创建 image 对象并赋值 src 链接
作用:让用户有快速的冲浪体验。
缺点:增加服务器压力,首页加载渲染时间长。
区别: 懒加载与预加载都有提高用于体验效果,但是优化体验效果的出发点不同,懒加载可以减轻服务器压力,但是预加载会增加服务器压力;

懒加载

预加载

浏览器渲染

浏览器解析 => 加载
DOM 树构建
CSS 树构建

渲染树构建

domTree + cssTree => renderTree 构建完毕 => 浏览器根据它绘制页面

  1. 渲染树每个节点都有自己的样式
  2. 不包含 display:none,以及 head 之类不需要绘制的节点
  3. 渲染树每一个节点都被当做一个盒子,具有内容填充,边距,边框,位置,大小等其他样式

回流重绘

回流:reflow,一定引起重绘。
重绘:repaint。不一定是回流产生的。

回流

因为节点的尺寸,布局,display:none/block.
上面这些改变时,渲染树中的一部分或者全部需要重新构建

引起回流的操作:

  1. DOM 节点的增删,位置变化
  2. 元素的尺寸,边距,填充,边框,宽高
  3. DOM 节点 display 显示与否
  4. 页面渲染初始化
  5. 浏览器窗口尺寸变化
  6. 向浏览器请求某些样式(offset,scroll,client,width,height,getComputedStyle()

重绘

回流后,浏览器根据新的渲染树重新绘制回流影响的部分或者全部节点。

时间线

从浏览器加载页面开始到完全结束的过程,按顺序发生的总流程叫时间线。

  1. 加载文档
  2. 解析文档,构建 DOM 树,同时构建 CSS 树
  3. 遇到 link 标签,开新线程,异步加载 css 外部文件 => 构建 Css 树
  4. 没有设置异步加载的 script 会阻塞文档解析,需要等 js 脚本加载且执行完成后,继续解析文档
  5. 异步加载 script,异步加载并执行,不阻塞解析文档。(不能使用 document.write)
  6. 解析文档遇到 src,创建加载线程,异步加载图片资源。
  7. 文档解析完成
  8. defer script JS 脚本执行。(async script 是解析完就执行)
  9. 监听解析完成的事件(DOMContentLoaded 事件,文档解析后触发)
  10. async script 加载执行完毕,img 等资源加载完毕,window 对象中的 onload 触发。
  11. window.onload 在所有资源加载完毕才触发。

BOM

浏览器对象模型。
表示由浏览器(主机环境)提供的用于处理文档(document)之外的所有内容的其他对象。
包括的内容有: navigator,screen,location,frames,history,XMLHttpRequest 等.
函数 alert/confirm/prompt 也是 BOM 的一部分:它们与文档(document)没有直接关系,但它代表了与用户通信的纯浏览器方法。

BOM 和 DOM 的区别

DOM:

  • 文档对象模型顶
  • 级对象是 document
  • 可以用来操作 html 页面的元素
  • 标准化是 w3c 来制定

BOM:

  • 浏览器对象模型
  • 顶级对象是 window
  • 用来和浏览器之间进行交互
  • 是由各浏览器厂商在各自浏览器上定义,没有一个统一的标准
  • 其实 DOM 对象就相当于是 BOM 儿子,因为 DOM 对象的操作都是由浏览器来执行的

作用

BOM 就是提供了一些访问窗口对象的一些方法,我们可以用它来移动窗口位置、改变窗口大小、打开新窗口、关闭窗口、弹出对话框、进行导航以及获取客户的一些信息如:浏览器品牌版本、屏幕分辨率….等等

常用方法

  1. prompt():弹窗输入
  2. alert():弹窗输出
  3. confirm:带确定取消的提示框,分别返回 true,false
  4. close:关闭当前浏览器窗口.
  5. open:打开一个新窗口
  6. setTimeout:延时器
  7. setInterval:定时器.

event

事件句柄,代表事件的状态,比如事件在其中发生的元素、键盘按键的状态、鼠标的位置、鼠标按钮的状态。简单说就是事件在发生时进行的一个操作。
onblur; 元素失去焦点。
onchange; 域的内容被改变。
onclick; 当用户点击某个对象时调用的事件句柄。
onfocus; 元素获得焦点。
onmousedown;鼠标按钮被按下。
onmousemove;鼠标被移动。
onmouseout;鼠标从某元素移开。
onmouseover;鼠标移到某元素之上。

History 对象

length; 查看浏览器的历史访问的网页的个数;
back(); 加载 history 列表中的前一个 url
forward();加载 history 列表中的下一个 url
go(); 加载 history 列表中的某个具体页面
go(0);相当于刷新页面

location 对象

封装当前打开窗口的 url

1
2
3
4
5
6
7
8
9
console.log(location);
console.log(location.href); //完整的URL路径
console.log(location.protocol); //协议名
console.log(location.hostname); //主机名
console.log(location.port); //端口号
console.log(location.host); //主机名+端口号
console.log(location.pathname); //文件路径
console.log(location.search); //从?开始的参数部分
console.log(location.hash); //从#开始的锚点部分

Screen 对象

height;屏幕的像素高度
width;屏幕的像素宽度
availHeight;屏幕的像素高度减去系统部件高度之后的值(只读)
availWidth;屏幕的像素宽度减去系统部件宽度之后的值(只读)
availLeft;未被系统部件占用的最左侧的像素值(只读)[chrome 和 firefox 返回 0,IE 不支持]
availTop;未被系统部件占用的最上方的像素值(只读)[chrome 和 firefox 返回 0,IE 不支持]

userAgent;包含浏览器的名称、内核、版本号等。
onLine;表示是否连接到了因特网。

坐标

offsetX,clientX,pageX,scrollX,getBoundleClientRect

offset

offsetParent 指对最近的一个有定位属性的元素。
offsetTop: 从元素上边框到 offsetParent 上边框的距离。
offsetWidth:返回元素的宽度,包括 padding,border。
offsetX:鼠标距离当前元素左边框的距离。

page

page 只有两个鼠标相关的属性
pageX: 鼠标距离文档顶部的距离,包括滚动滚去的。
pageY: 鼠标距离文档左边的距离。

client

clientTop: 元素的上边框高度。
clientLeft: 元素右边框宽度,如果有滚动条,包括滚动条。
clientWidth: 元素宽度,padding+content
clientX: 鼠标距离视口顶部高度

scroll

scrollTop: 上下滚动条滚动的距离
scrollLeft: 左右滚动条滚动的距离
scrollWidth: 元素实际宽度,content+padding+scrollLeft 最大值
scrollHeight: 元素实际高度,content+ padding+ scrollTop 最大值
scrollX: 文档在水平方向已滚动的像素值

getBoundingClientRect

获取页面中某个元素上下左右相对于视口的位置。
缺点:非实时。

查看计算样式 getComputedStyle

window.getComputedStyle(elem, null)[prop]
IE8:
elem.currentStyle

1
window.getComputedStyle(div, null)["height"]; // "200px"

坐标系区别

一、clientX、clientY
点击位置距离当前 body 可视区域的 x,y 坐标
二、pageX、pageY(IE9 以下不支持)
对于整个页面来说,包括了被卷去的 body 部分的长度
三、screenX、screenY
点击位置距离当前电脑屏幕的 x,y 坐标
四、offsetX、offsetY(包含边框,safari 不包含边框)
相对于带有定位的父盒子的 x,y 坐标
五、x、y
同 clientX,不建议使用。兼容性不好。
六、layerX、layerY
同 pageX,不建议使用。IE11 以下同 client。

滚动条距离

常规:window.pageXOffset/pageYOffset
IE9 以下:
document.body.scrollLeft/scrollTop
document.doucmentElement.scrollLeft/scrollTop

可视区域

常规:window.innerWidth/innerHeight
IE9/IE8:
标准:document.doucmentElement.clientWidth、clientHeight
怪异:document.body.clientWidth、clientHeight

浏览器兼容模式

怪异模式和标准模式

html 文件开头写了<!DOCTYPE html>就是标准模式,否则是怪异模式,就是向后兼容模式。

DOM

文档对象模型。
getElementById() 返回带有指定 ID 的元素。
getElementsByTagName() 返回包含带有指定标签名称的所有元素的节点列表(集合/节点数组)。
getElementsByClassName() 返回包含带有指定类名的所有元素的节点列表。
appendChild() 把新的子节点添加到指定节点。
removeChild() 删除子节点。
replaceChild() 替换子节点。
insertBefore() 在指定的子节点前面插入新的子节点。
createAttribute() 创建属性节点。
createElement() 创建元素节点。
createTextNode() 创建文本节点。
getAttribute() 返回指定的属性值。
setAttribute() 把指定属性设置或修改为指定的值。
innerHTML - 节点(元素)的文本值
parentNode - 节点(元素)的父节点
childNodes - 节点(元素)的子节点
attributes - 节点(元素)的属性节点

Element

这是一个基类。所有 Document 对象下的对象都继承自它。

getElementsByClassName()

方法返回一个即时更新的(live)HTMLCollection,这是个集合,也即是类数组对象。包含了所有拥有指定 class 的子元素

getElementByTagName()

返回包含带有指定标签名称的所有元素的节点列表(集合/节点数组)

getElementById()

返回一个匹配特定 ID 的元素。

getAttributeNode()

返回指定元素的指定属性节点

querySelector()

方法返回文档中与指定选择器或选择器组匹配的第一个 Element 对象。如果找不到匹配项,则返回 null。

querySelectorAll(selector)

选择器是一个 css 选择器。
返回与指定的选择器组匹配的文档中的元素列表 (使用深度优先的先序遍历文档的节点)。
返回的对象是 NodeList 。这也是个类数组对象。

createElement

创建元素。

1
document.createElement("div");

appenChild

属于 Node.prototype。
增加子节点。如果操作已有节点,就是剪切节点。

insertBefore(a,b)

插入节点。

1
c.insertBefore(a, b); // 在父节点c内,子节点b之前插入子节点a

removeChild

删除子节点。

节点

节点包括 nodeType,nodeName,nodeValue.

nodeType

属性返回节点的类型,nodeType 只读.

元素类型 NodeType
元素 1
属性 2
文本 3
注释 8
文档 9

每个节点都有一个 childNodes 属性,里边是一个 NodeList 对象(类数组),它是基于 DOM 结构动态执行查询的结果,因此 DOM 结构的变化能够自动反映在 NodeList 对象中。
同样每个节点还有一个 children 的属性,它与 childNodes 的区别就是,childNodes 存储的是 NodeList 对象,而 children 返回的是 HTMLCollection 对象。NodeList 存储的不只是元素节点,也有文本节点、注释节点等等,而 HTMLCollection 存储的只有元素节点。

1
2
3
4
5
6
7
8
<div id="test">hello <span>word</span></div>

<script>
let btn = document.getElementById("test");
console.log(btn.childNodes, btn.children);
//NodeList(3) [text, span, text]
//HTMLCollection(1) [span]
</script>

nodeName

属性名并不只是存在与属性节点,任何节点都有属性名。nodeName 属性规定节点的名称。他们的属性名如下:

元素节点的 nodeName 与标签名相同
属性节点的 nodeName 与属性名相同
文本节点的 nodeName 始终是 #text
文档节点的 nodeName 始终是 #document

属性值 nodeValue

nodeValue 属性规定节点的值,也就是每个属性名,就有会他对应的属性值。

元素节点的 nodeValue 是 undefined 或 null
文本节点的 nodeValue 是文本本身
属性节点的 nodeValue 是属性值

属性节点 AttributeNode

什么是属性?我们元素中的内置对象,就是属性节点。比如 style,class, id,都称为元素的属性节点。

Element.getAttributeNames();//获取属性名
Element.getAttribute(“class”);//获取 class 的属性值
Element.attributes.item(0);//获取元素第一个属性键值对
Element.hasAttribute(“class”);//判断是否有 class 属性
Element.setAttribute(“class”, “active”);//设置 class 属性

比如我们需要重置元素 class 的值,我们就可以通过 setAttribute 重新给 class 赋值。
当然还有一个内置 class 对象可以操作:Element.classList.add(“active”, “show”)
介绍到这里,你能想象一下 vue 的的 v-if 或者 v-on 是怎么实现的吗?简单的来说,他就是把他当成一个元素属性,获取到对应的标识,再去解析。

innerHTML

可以对 DOM 节点取值或者赋值。
取值会返回 html 字符串。
赋值追加可以执行div.innerHTML += '123'
会被解析。

innerText

返回字符实体。带转义字符的。
如果赋值,就会替换原内容

textContext

返回源字符。不带转义字符,页面中显示的。
不会被解析。

区别

textContext 获取所有元素的内容。
innerText 只获取给人看的内容。
innerText 会受到 CSS 的影响,reflow。
innerHTML 容易有安全性问题。

自定义属性 data-*

1
2
3
<div data-name="abc" data-age="18">
aaa
</div>

可以通过 dataset 访问。div.dataset.name = 'abc'
或者getAttributediv.getAttribute('data-name')

类数组对象

所谓类数组对象,就是指可以通过索引属性访问元素并且拥有 length 属性的对象。
常见的类数组对象:argumentsnodeListtypedArray

区别

事件机制

事件触发过程是怎样的? 什么是事件代理?

事件触发(事件流 )

三个阶段: 捕获阶段 => 事件(目标)阶段 => 冒泡阶段

事件源

事件作用在哪个元素上,那个元素就是事件源。

事件对象

就是事件函数返回的 e

target 和 currentTarget

target: 事件源对象。指当前真正点击的元素
currentTarget: 事件绑定的元素.

事件绑定

有三种方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 1. 在DOM中直接绑定
<button onclick="fn()">click</button>
<script>
function fn(){
console.log('123')
}
</script>

// 2. 在js中绑定
<button id="btn2">click</button>
// 兼容性好,绑定多个事件时,以最后一个为准
document.getElementById("btn2").onclick = function(){
console.log('123')
}

// 3. 绑定事件监听函数
document.getElementById('btn2').addEventListener('click', function(){
console.log('123')
}) // 默认false,冒泡

事件监听和事件绑定

事件监听和事件绑定的最大区别就是事件监听可以给一个事件监听多个函数操作,而事件绑定只有一次.

1
2
3
4
5
6
// 可以监听多个,不会被覆盖
eventTarget.addEventListener("click", () => {});
eventTarget.addEventListener("click", () => {});

eventTarget.onclick = function () {};
eventTarget.onclick = function () {}; // 第二个会把第一个覆盖

事件冒泡和事件捕获

顺序:先执行捕获,后执行冒泡。
区别:捕获是父级捕获子级的事件,冒泡是子级向父级冒泡。

1
2
3
4
5
6
7
8
9
box.addEventListener(
"click",
function () {
console.log(1);
},
false
);
// 第三个参数不写,或者false 就是事件冒泡
// true 就是事件捕获

没有冒泡和捕获事件的: focus, blur, scroll,change,submit,reset,select.

阻止事件冒泡和捕获

stopPropagation 可以阻止事件传播,但是不能阻止事件发生.
例如对链接的点击仍会处理.

1
2
3
event.stopPropagation(); // 支持IE9+
event.stopImmediaPropagation();
event.cancelBubble = true; // 支持IE8

取消默认事件

阻止事件触发后的默认动作的发生.

1
2
3
4
5
6
// 取消右键菜单
document.oncontextmenu = function () {
// return false
// w3c: e.preventDefalut() // IE9
// e.returnValue = false; // IE8
};

事件代理(委托)

如果我们有许多以类似方式处理的元素,那么就不必为每个元素分配一个处理程序 —— 而是将单个处理程序放在它们的共同祖先上
在处理程序中,我们获取 event.target 以查看事件实际发生的位置并进行处理。
在 ul 列表绑定事件,就不用在每个 li 元素上单独绑定了,因为会冒泡到父元素上,通过 event.target 拿到.

1
2
3
4
5
6
7
8
9
10
11
12
13
<body>
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</body>
<script>
const ul = document.getElementById("ul");
ul.addEventListener("click", function (e) {
console.log(e.target.value);
});
</script>

事件委托的特点
1、提高 JS 性能
2、动态的添加 DOM 元素

a 标签事件

1
<a href="javascript:void(0)"></a>

上面执行void(0),就是 return 0。也就不会跳转。

1
2
// 锚点,点击跳转到顶部
<a href="#">

HTML5

input 的自动聚焦(autofocus)

拖动 drag

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<div class="dropzone"></div>

<script type="text/javascript">
var dragged;

document.addEventListener(
"dragstart",
function (e) {
// 保存拖动元素的引用
dragged = e.target;
},
false
);

document.addEventListener(
"dragenter",
function (e) {
if (e.target.className == "dropzone") {
e.target.style.backgroundColor = "purple";
}
},
false
);

document.addEventListener(
"dragleave",
function (e) {
if (e.target.className == "dropzone") {
e.target.style.backgroundColor = "";
}
},
false
);

document.addEventListener(
"dragover",
function (e) {
//阻止默认行为,让允许投掷
e.preventDefault();
},
false
);

document.addEventListener(
"drop",
function (e) {
e.preventDefault();
if (e.target.className == "dropzone") {
e.target.style.backgroundColor = "";
dragged.parentNode.removeChild(dragged);
e.target.appendChild(dragged);
}
},
false
);
</script>

dataTransfer:拖拽目标的信息。

地理位置

1
2
3
4
5
6
7
function success(e) {
console.log("获取位置成功", e);
}
function failure(e) {
console.log("获取位置失败", e);
}
window.navigator.geolocation.getCurrentPosition(success, failure);

设备信息

devicemotion
加速度:devicemotion.acceleration.x
设备方向:deviceorientation.alpha,beta,gamma

文件信息

1
<input type="file" id="flieInput" />

image.png
图片预览
image.png

Web Worker

介绍

运行在主线程之外的脚本,
分为专用线程和共享线程

用途

由于 js 是单线程,遇到复杂运算,就会影响渲染进程,造成页面卡顿.
就可以把复杂运算放到 webworker 中,等到计算结束,将结果返回给主线程.

限制

  1. 同源限制: 分配给 webworker 的脚本文件必须与主线程同源
  2. 文件限制: workers 线程是运行在后台的,它所加载的文件必须是线上的,不能是本地的
  3. DOM 限制: workers 线程不能直接操作 DOM,如果要处理 DOM 对象,必须是将结果返回给主线程,由主线程操作
  4. 脚本限制: 不能使用 alert 和 confirm 方法,可以使用 ajax 请求
  5. 通信限制: workers 线程和主线程不在同一个上下文,无法使用 window 对象,不能直接通信,只能通过消息.

用法

在主线程中创建 worker,先判断是否支持 worker

1
2
3
if (window.worker) {
// 支持worker
}

专用线程由Worker()方法创建,接收两个参数,第一个参数必填,是脚本的位置.第二个参数是可选的配置对象.
可以指定type,credentials,name三个属性.

1
2
let worker = new Worker("worker.js");
// let worker = new Worker('worker.js', {name: 'dedicateWorker'});

注意: 因为 webworker 有同源策略,本地调试时也需要启动本地服务器访问.

数据传递

webworker 和主线程都是通过postMessage()方法发送消息,通过 onmessage 事件接收消息.

  • 此过程中的数据不是共享而是复制的.
  • ErrorFunction对象不能被结构化克隆算法复制,被报错
  • PostMessage 一次只能发一个对象.多个参数可以包装成数组或对象传递
1
2
3
4
5
6
7
8
9
10
11
12
13
// 主线程
var worker = new Worker("worker.js");
worker.postMessage([10, 24]);
worker.onmessage = function (e) {
console.log(e.data);
};

// Worker 线程
onmessage = function (e) {
if (e.data.length > 1) {
postMessage(e.data[1] - e.data[0]);
}
};

在子线程中,self 和 this 都可以表示子线程的全局对象.
下面 4 种方法通用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 写法 1
self.addEventListener("message", function (e) {
// ...
});

// 写法 2
this.addEventListener("message", function (e) {
// ...
});

// 写法 3
addEventListener("message", function (e) {
// ...
});

// 写法 4
onmessage = function (e) {
// ...
};

共享线程

主线程通过 MessagePort 访问专用线程和共享线程。专用线程的 port 会在线程创建时自动设置,并且不会暴露出来。与专用线程不同的是,共享线程在传递消息之前,端口必须处于打开状态。

start() 方法是与 addEventListener 配套使用的。如果我们选择 onmessage 进行事件监听,那么将隐含调用 start() 方法。

在传递消息时,postMessage() 方法和 onmessage 事件必须通过端口对象调用。另外,在 Worker 线程中,需要使用 onconnect 事件监听端口的变化,并使用端口的消息处理函数进行响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 主线程
sharedWorker.port.postMessage([10, 24]);
sharedWorker.port.onmessage = function (e) {
console.log(e.data);
};

// Worker 线程
onconnect = function (e) {
let port = e.ports[0];

port.onmessage = function (e) {
if (e.data.length > 1) {
port.postMessage(e.data[1] - e.data[0]);
}
};
};

关闭 worker

可以在主线程中使用 terminate() 方法或在 Worker 线程中使用 close() 方法关闭 worker。这两种方法是等效的,但比较推荐的用法是使用 close(),防止意外关闭正在运行的 Worker 线程。Worker 线程一旦关闭 Worker 后 Worker 将不再响应。

1
2
3
4
5
6
7
8
// 主线程
worker.terminate();

// Dedicated Worker 线程中
self.close();

// Shared Worker 线程中
self.port.close();

错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 主线程
worker.onerror = function () {
// ...
};

// 主线程使用专用线程
worker.onmessageerror = function () {
// ...
};

// 主线程使用共享线程
worker.port.onmessageerror = function () {
// ...
};

// worker 线程
onerror = function () {};

上下文

Worker 工作在一个 WorkerGlobalDataScope 的上下文中。每一个 WorkerGlobalDataScope 对象都有不同的 event loop。这个 event loop 没有关联浏览器上下文(browsing context),它的任务队列也只有事件(events)、回调(callbacks)和联网的活动(networking activity)。
每一个 WorkerGlobalDataScope 都有一个 closing 标志,当这个标志设为 true 时,任务队列将丢弃之后试图加入任务队列的任务,队列中已经存在的任务不受影响(除非另有指定)。同时,定时器将停止工作,所有挂起(pending)的后台任务将会被删除。

路由栈缓存与登录返回

通过getCurrentPages获取 VNode,遍历后得到其中的 route.通过uni.navigateBack()跳转回之前页面.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
backPage() {
let pages = getCurrentPages()
let routerList = pages.map(item => {
return item.route;
});
let delta = 0
//查找数组中有多少登录页
routerList.forEach(url => {
if (url == 'pages/workbench/login/index') {
delta++
}
})
//如果路由中全是登录页,则跳转到首页
if (delta == routerList.length) {
this.$u.route({
url: 'pages/index/index',
type: 'tab'
})
} else {
//否则就返回相应的页面
uni.navigateBack({
delta,
});
}
},

生命周期

应用生命周期

只在App.vue中.
onlaunch 之类

页面生命周期

onload 之类

组件生命周期

created 之类

Page 页面生命周期函数执行顺序

beforeCreate => onLoad => onShow => created => beforeMount => onReady => mounted

刷新数据后

beforeUpdate => updated

监听实体按键后退

页面生命周期中的onBackPress.
注意: 由于 uni.navigateBack() 同样会触发 onBackPress 函数。因此在 onBackPress 中直接调用 uni.navigateBack() 并始终返回 true 会引发死循环。
此时,需要根据 onBackPress 的回调对象中的 from 值来做处理,当来源是 ‘navigateBack’ 时,返回 false。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template>
<view>
</view>
</template>

<script>
export default {
data() {
return {};
},
onBackPress(options) {
if (options.from === 'navigateBack') {
return false;
}
this.back();
return true;
},
methods: {
back() {
uni.navigateBack({
delta: 2
});
}
},
}
</script>

<style>

</style>

封装中断请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//在请求类中设置requestTask,uiapp存在中止方法requestTask.abort()
//将所有请求放置到请求数组中,当某一项请求完成则清除,

let requestTask = null;
return new Promise((resolve, reject) => {
options.complete = (response) => {

Vue.prototype.requestArray = Vue.prototype.requestArray.filter(item => {
return item.id !== requestTask.id
});
//...
requestTask = uni.request(options);
requestTask.id = guid(); //使用随机数
Vue.prototype.requestArray = [...Vue.prototype.requestArray,requestTask]

})

//调用
clearTask(){
this.requestArray.map(item => {
item && item.abort()
})
},

//调用时添加到对应发请求处,如果该请求需要终止,就可以终止

uniapp 中 eCharts 在 h5 端导致 tooltip 不显示问题

因为会在 h5 端先判断环境,直接为 wx 环境导致,所以应将 wx 设置为undefined{}.

1
2
3
4
5
6
7
8
9
//main.ts
window.wx = {}
//如果是ts,全部变量声明也要写
//xx.d.ts
declare global {
interface Window {
wx?: any;
}
}

数组添加优雅写法

1
2
3
(todo) => {
setTodoList((todoList) => [...todoList, todo]);
}; //react hooks

金额格式化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*
* formatMoney(s,type)
* 功能:金额按千位逗号分隔,负号用'-'号
* 参数:s,需要格式化的金额数值.
* 参数:type,判断格式化后的金额是否需要小数位.
* 返回:返回格式化后的数值字符串.
*/
function formatMoney(s, type) {
var result = s;
if (s < 0) s = 0 - s;
if (/[^0-9\.]/.test(s)) return "0.00";
if (s == null || s == "null" || s == "") return "0.00";
if (type > 0) s = new Number(s).toFixed(type);
s = s.toString().replace(/^(\d*)$/, "$1.");
s = (s + "00").replace(/(\d*\.\d\d)\d*/, "$1");
s = s.replace(".", ",");
var re = /(\d)(\d{3},)/;
while (re.test(s)) s = s.replace(re, "$1,$2");
s = s.replace(/,(\d\d)$/, ".$1");
if (type == 0) {
var a = s.split(".");
if (a[1] == "00") {
s = a[0];
}
}
if (result < 0) result = "-" + s;
else result = s;
return result;
}

手机号固话正则校验

支持手机号码,3-4 位区号,7-8 位直播号码,1-4 位分机号,支持支持手机号码,3-4 位区号,7-8 位直播号码,1-4 位分机号

1
/(^(1[3|4|5|7|8|9][0-9]\d{8})$|^((\d{7,8})|(\d{4}|\d{3})-(\d{7,8})|(\d{4}|\d{3})-(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1})|(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1}))$)/g;

JS 去重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//filter去重
this.expertList = res.filter((item,index) =>{
return res.findIndex(i =>item.userId == i.userId) == index
})

//reduce去重
const hash = {};
const newArray = arr.reduce((item, next)=>{
hash[next.key] ? '' : hash[next.key] = true && item.push(next);
return item;
},[])

//new Set去重
unique(arr) {
const res = new Map();
return arr.filter((arr) => !res.has(arr.id) && res.set(arr.id, 1))
}

多组对象转化数组(reduce)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
let obj = {
normal: {
//标准值
timeOpenRate: 93.6, //时间开动率
funOpenRate: 95.8, //性能开动率
successRate: 97.5, //合格品率
oeeRate: 87.43, //OEE
},
actual: {
//实际值
timeOpenRate: 100.0,
funOpenRate: 0.0,
successRate: 0.0,
oeeRate: 0.0,
},
last: {
//同期
timeOpenRate: 0.0,
funOpenRate: 0.0,
successRate: 0.0,
oeeRate: 0.0,
},
};

const list = Object.keys(obj.normal).reduce((pre, next) => {
const now = {
type: next,
};
Object.keys(obj).map((item) => {
now[item] = obj[item][next];
});
pre.push(now);
return pre;
}, []);
console.log(`r`, list);

//方法二
Object.keys(obj.normal).map((e) => ({
type: e,
...Object.fromEntries(
Object.keys(obj).map((key) => {
return [key, obj[key][e]];
})
),
}));

// [
// {
// type: 'timeOpenRate',
// actual:'', //actual.timeOpenRate的值
// normal:'',//normal.timeOpenRate的值
// last:'',//last.timeOpenRate的值
// },
// {
// type: 'funOpenRate',
// actual:'',//actual.funOpenRate的值
// normal:'',//normal.funOpenRate的值
// last:'',//last.funOpenRate的值
// },
// {
// type: 'successRate',
// actual:'',
// normal:'',
// last:'',
// },
// {
// type: 'oeeRate',
// actual:'',
// normal:'',
// last:'',
// },
// ]

多数组对象转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// dimensions: ['shiftDate', 'A', 'B', 'C', 'D'],

// [
// {
// shiftDate: '20211123',
// B:''oeeRate, //item.oeeRate的值
// D:''oeeRate,//item.oeeRate的值
// },
// {
// shiftDate: '20211112',
// A:''oeeRate, //item.oeeRate的值
// B:''oeeRate,//item.oeeRate的值
// },
// ]

let arr = [
[
{
timeOpenRate: 100.0,
teamGroup: "B",
funOpenRate: 4738500.0,
successRate: 0.0,
shiftDate: "20211123",
oeeRate: 0.0,
},
{
timeOpenRate: 96.67,
teamGroup: "D",
funOpenRate: 6700200.0,
successRate: 0.0,
shiftDate: "20211123",
oeeRate: 0.0,
},
],
[
{
timeOpenRate: 100.0,
teamGroup: "A",
funOpenRate: 170908400.0,
successRate: 0.0,
shiftDate: "20211112",
oeeRate: 0.0,
},
{
timeOpenRate: 100.0,
teamGroup: "B",
funOpenRate: 181821900.0,
successRate: 0.0,
shiftDate: "20211112",
oeeRate: 0.0,
},
],
];
arr.map((item) => {
const obj = {};
item.map((i) => {
obj.shiftDate = i.shiftDate;
obj[i.teamGroup] = i.oeeRate;
});
return obj;
});

对象映射 mapping

1
2
3
4
5
6
7
8
9
10
11
//订单状态颜色
handlerStatus(status) {
const mapping = {
10: "info",
20: "warning",
30: "error",
40: "success",
50: "primary",
};
return mapping[status];
},

同名属性赋值

1
2
3
4
// 遍历child(未赋值)中的属性,将parent中的同名属性赋值给child
Object.keys(this.child).map((key) => {
this.child[key] = this.parent[key];
});

同名属性赋值(浅拷贝)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const {
actualCustomerCode,
actualCustomerName,
demandId,
demandMonth,
machineCode,
productParentCode,
productParentName,
saleNetType,
saleNetworkCode,
sellerCode,
} = form;
this.ssdhOrderDemand = Object.assign(
{},
{
actualCustomerCode,
actualCustomerName,
demandId,
demandMonth,
machineCode,
productParentCode,
productParentName,
saleNetType,
saleNetworkCode,
sellerCode,
}
);

数组对象中按顺序插入另一个数组的子项

1
2
3
4
5
6
7
8
9
10
let list = [
{ a: 1, b: 2 },
{ a: 3, b: 4 },
{ a: 5, b: 6 },
];

let subList = [{ c: 11 }, { c: 22 }, { c: 33 }];
let arr = list.map((item, index) => Object.assign({}, item, subList[index]));

console.log(arr);

对象转数组

1
2
3
4
5
let obj = { a: 1, b: 2 };
let arr = [{ a: 1 }, { b: 2 }];
Object.keys(obj).map((i) => ({ [i]: obj[i] }));

Object.entries(obj).map(([k, v]) => ({ [k]: v }));

||运算符的顺序

1
2
3
4
5
6
let a = "";
let b = "";
let c = "cc";
a = b || c ? 123 : 321;

console.log(a); //123

上面示例的顺序是先判断 b 或 c 是否存在,再进行三元运算

1
2
3
4
let a = "";
let b = "bb";
let c = "cc";
console.log(a == b || c); //cc

最后判断不是a是否等于 b 或 c,而是 a 是否等于 b,c 是否存在.
正常应该是两边都判断是否相等

1
console.log(a == b || a == c);

return

只有一层 if 时,可以使用return中断.
多层 if 时,建议设置变量,例如flag

1
if (flag) return;

filter

1
2
3
4
5
6
7
//filter =>map,push
let arr = [];
let item = "A";
// this.category.map(i => {
// item.includes(i.key) ? arr.push(i) : "";
// });
arr = this.category.filter((i) => item.includes(i.key));

toFixed

oFixed()方法可把 Number 四舍五入为指定小数位数的数字.

reduce

接收 4 个参数,acc(累加器),cur(当前值),idx(当前索引),src(源数组)

1
2
3
4
5
6
7
8
9
10
11
12
13
//计算数组中每个元素出现的次数
var names = ["Alice", "Bob", "Tiff", "Bruce", "Alice"];

var countedNames = names.reduce(function (allNames, name) {
if (name in allNames) {
allNames[name]++;
} else {
allNames[name] = 1;
}
return allNames;
}, {});
// countedNames is:
// { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 如果code相同,就给name字符串加1做出区分.(比如,南风仓库1)
let data = [
{
class: "a",
code: "H1",
name: "东仓库",
},
{
class: "b",
code: "H2",
name: "南仓库",
},
{
class: "c",
code: "H2",
name: "南仓库",
},
{
class: "d",
code: "H3",
name: "西仓库",
},
];

const r = data.reduce(
(prev, next) => {
const length = prev.filter((item) => item.code === next.code).length || "";
prev.push({
...next,
name: `${next.name}${length}`,
});
return prev;
},
[data[0]]
);

console.log(r);
1
2
3
4
5
6
7
8
//数组去重
let myArray = ["a", "b", "a", "b", "c", "e"];
let ordArray = myArray.reduce((acc, cur) => {
if (acc.indexOf(cur) === -1) {
acc.push(cur);
}
return acc;
}, []);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let list = [
{
arr: [1, 2, 3],
},
{
arr: [1, 2, 3, 4, 5],
},
{
arr: [1, 2],
},
];

//如何取出最长的一个数组出来

list.reduce((res, { arr }) => (res.length > arr.length ? res : arr), []);

fill 数据填充

后端只返回空数组或者短于要求的数组,其他填充为 0,或者固定值.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let list = [
{
arr: [1, 2, 3],
},
{
arr: [1, 2, 3, 4, 5],
},
{
arr: [1, 2],
},
];
let arr = [...list, ...[...Array(12 - list.length)].fill(0)].fill(
{ value: 0 },
list.length,
12
);

判断空对象

1
2
3
4
5
6
7
function isEmpty(value) {
return (
value === null ||
value === undefined ||
(typeof value === "object" && Object.keys(value).length === 0)
);
}

修改数组对象

1
2
3
4
const { hyBlock, cpBlock, txBlock } = data.data
this.list = hyBlock.map(item =>({label: item.labelTitle, value: item.labelId}))
//同名可以使用解构
this.list = hyBlock.map({labelTitle, labelId}) =>({labelTitle, labelId}))

文章摘要截取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//解析字符串
htmlSlice(content){
if(content){
let re1 = new RegExp("<.+?>","g");//匹配html标签的正则表达式,"g"是搜索匹配多个符合的内容
let re2 = new RegExp("&.+?;","g");//匹配html标签的正则表达式,"g"是搜索匹配多个符合的内容
let msg1 = content.replace(re1,'');//执行替换成空字符
let msg = msg1.replace(re2,'"');//执行替换成空字符

if(msg.length > 100){
msg = msg.substr(0,100)
}
console.log(msg.length,"msg");
return msg
}
}

//css限定行数
.content {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
}

表单重置

1
2
3
4
5
6
7
8
9
10
11
resetForm(opt) {
Object.keys(opt).map((item) => {
if (opt[item] instanceof Array) {
opt[item] = []
} else if (opt[item] instanceof Object) {
opt[item] = {}
} else {
opt[item] = ''
}
})
},

使用 picker 组件自制时间区间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//view
<picker mode="date" :value="defaultTime" :end="startDate" fields="day" @change="getData('start',$event)">
<view class="line-box1 u-margin-right-10">{{ "起始日期" }}</view>
</picker>
<picker mode="date" :end="endDate" fields="day" @change="getData('end',$event)">
<view class="line-box1 u-margin-right-10">{{ "截止日期" }}</view>
</picker>
//script
data() {
const timeD = this.$u.timeFormat(
new Date().getTime() - 7 * 24 * 3600 * 1000 //,默认7天范围
);
},
computed: {
startDate() {
//要return的日期比endDate数值小或===
return this.endDate;
},
endDate() {
let date = new Date();
let year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
return `${year}-${month}-${day}`;
},
},

格式化字符串时间

1
2
3
4
//将'20211102'格式化为'2021-11-02'
formDateStr(str){
return str.slice(0,4)+'-'+str.slice(4,6)+'-'+str.slice(6)
},

仅修改对象数组里某个值

1
2
3
4
5
6
7
8
9
const list = [
{ name: "aaa", age: 10 },
{ name: "bbb", age: 11 },
{ name: "ccc", age: 12 },
];
const newList = list.map((item) => ({
...item,
age: item.age + 1,
}));

判断类型的方法 toString()

toString() 是 Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]] 。这是一个内部属性,其格式为 [object Xxx] ,其中 Xxx 就是对象的类型。

对于 Object 对象,直接调用 toString() 就能返回 [object Object] 。而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
Object.prototype.toString.call(""); // [object String]
Object.prototype.toString.call(1); // [object Number]
Object.prototype.toString.call(true); // [object Boolean]
Object.prototype.toString.call(Symbol()); //[object Symbol]
Object.prototype.toString.call(undefined); // [object Undefined]
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call(new Function()); // [object Function]
Object.prototype.toString.call(new Date()); // [object Date]
Object.prototype.toString.call([]); // [object Array]
Object.prototype.toString.call(new RegExp()); // [object RegExp]
Object.prototype.toString.call(new Error()); // [object Error]
Object.prototype.toString.call(document); // [object HTMLDocument]
Object.prototype.toString.call(window); //[object global] window 是全局对象 global 的引用

使用函数封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export function filterClassName(names) {
const type = Object.prototype.toString.call(names).slice(8, -1).toLowerCase();
switch (type) {
case "array":
return names.length ? names.join(" ") : "";
case "object":
const keys = Object.keys(names);
let newClass = "";
return keys.length
? keys.forEach((key) => (newClass += names[key] + " "))
: "";
case "string":
return names;
case "number":
return names.toString();
default:
return "";
}
}

Json 转 tree

一维数组转树形结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
const list = [
{
name: "Tom",
age: "25",
team: "A",
item: "SSR",
itemNum: "300",
},
{
name: "Tom",
age: "25",
team: "A",
item: "SSk",
itemNum: "500",
},
{
name: "Jim",
age: "22",
team: "B",
item: "KKR",
itemNum: "300",
},
{
name: "Jim",
age: "22",
team: "B",
item: "KKS",
itemNum: "500",
},
];
const finList = [
{
name: "Tom",
age: "25",
team: "A",
itemGroup: [
{
item: "SSR",
itemNum: "300",
},
{
item: "SSk",
itemNum: "500",
},
],
},
{
name: "Jim",
age: "22",
team: "B",
itemGroup: [
{
item: "KKR",
itemNum: "300",
},
{
item: "KKS",
itemNum: "500",
},
],
},
];

function convert(list) {
let arr = [];
list.forEach((i, index) => {
let { name, age, team, item, itemNum } = i;
if (!arr[name]) {
arr[name] = {
name,
age,
team,
teamGroup: [],
};
}
arr[name].teamGroup.push({
item,
itemNum,
});
});
return Object.values(arr);
}
let aaa = convert(list);
console.log(aaa);

不同 id 的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
let flatArr = [
{ id: 1, title: "解忧杂货铺1", parent_id: 0 },
{ id: 2, title: "解忧杂货铺2", parent_id: 0 },
{ id: 3, title: "解忧杂货铺2-1", parent_id: 2 },
{ id: 4, title: "解忧杂货铺3-1", parent_id: 3 },
{ id: 5, title: "解忧杂货铺4-1", parent_id: 4 },
{ id: 6, title: "解忧杂货铺2-2", parent_id: 2 },
];
function convert(list) {
const res = [];
const map = list.reduce((res, v) => ((res[v.id] = v), res), {});
for (const item of list) {
if (item.parent_id === 0) {
res.push(item);
continue;
}
if (item.parent_id in map) {
const parent = map[item.parent_id];
parent.children = parent.children || [];
parent.children.push(item);
}
}
return res;
}
let returnTree = convert(flatArr);
console.log(returnTree);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
//使用categoryCode绑定传入 //v-if每个子项模板
<template>
<view>
<!-- <template v-if="categoryCode === 'A'"> -->
<view class="block-list align-center" v-for="i in productType" :key="i.key">
<view class="list-left">{{ i.name }}</view>
<view class="input-box bg-white weight-box">
<u-input placeholder="" v-model="i.value" />
</view>
</view>
<!-- </template> -->
</view>
</template>

<script>
export default {
name: "subitem",
props: {
categoryCode: {
type: String,
default: "",
},
item: {
type: Object,
default() {
return {};
},
},
},
data() {
return {
productType: [],
category: [
{ name: "内径", key: "coilInnerDia", value: "" },
{ name: "厚度/直径", key: "orderThick", value: "" },
{ name: "宽度", key: "width", value: "" },
{ name: "长度", key: "orderLength", value: "" },
{ name: "壁厚", key: "thickTbthDim", value: "" },
{ name: "外径", key: "coilInsideDia", value: "" },
{ name: "订货量", key: "netWeight", value: "" },
],
A: ["orderThick","width","orderLength","netWeight" ],
B: ["coilInnerDia","orderThick","width","orderLength","netWeight" ],
C: ["orderThick","width","orderLength","netWeight" ],
D: ["coilInnerDia","orderThick","width","orderLength","netWeight" ],
E: ["coilInnerDia","orderThick","width","orderLength","netWeight" ],
G: ["coilInnerDia","orderThick","width","orderLength","netWeight" ],
N: ["coilInnerDia","orderThick","width","netWeight" ],
Q: ["coilInnerDia","orderThick","width","netWeight" ],
R: ["orderLength","netWeight" ],
S: ["orderThick","orderLength","netWeight" ],
T: ["coilInnerDia","orderThick","width","netWeight" ],
U: ["orderLength","netWeight" ],
W: ["coilInsideDia","thickTbthDim","coilInnerDia","orderLength","netWeight" ],
X: ["orderThick","netWeight" ],
};
},
mounted() {
this.setCategory();
this.setValue();
},
methods: {
setCategory() {
const item = this.categoryCode
let arr = [];
this.category.map((i, index) => {
Object.keys(item).includes(i.key) ? arr.push(this.category[index]) : "";
});
this.productType = arr;
},
setValue() {
let arr = this.productType
// const mapping = {
// A: "gp",
// B: "rz",
// C: "zhb",
// D: "rzsx",
// E: "lz",
// G: "rdx",
// N: "wqx",
// Q: "qx",
// R: "gg",
// S: "bc",
// T: "ct",
// U: "xg",
// W: "wfgg",
// X: "xc",
// };
// const arr = this[mapping[this.categoryCode]];
// let arr = this.categoryCode
if(arr.length){
arr.map((i) => {
i.value = this.item[i.key];
});

}
},
},
};
</script>

<style>
.input-box {
border: 1px solid #eee;
border-radius: 10rpx;
padding: 0 20rpx;
}
.weight-box {
width: 50vw;
}
</style>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
//使用categoryCode绑定传入 //v-if每个子项模板
<template>
<view>
<!-- <template v-if="categoryCode === 'A'"> -->
<view class="block-list align-center" v-for="i in productType" :key="i.key">
<view class="list-left">{{ i.name }}</view>
<view class="input-box bg-white weight-box">
<u-input placeholder="" v-model="i.value" />
</view>
</view>
<!-- </template> -->
</view>
</template>

<script>
export default {
name: "subitem",
props: {
// categoryCode: {
// type: String,
// default: "",
// },
item: {
type: Object,
default() {
return {};
},
},
},
data() {
return {
productType: [],
category: [
{ name: "内径", key: "coilInnerDia", value: "" },
{ name: "厚度/直径", key: "orderThick", value: "" },
{ name: "宽度", key: "width", value: "" },
{ name: "长度", key: "orderLength", value: "" },
{ name: "壁厚", key: "thickTbthDim", value: "" },
{ name: "外径", key: "coilInsideDia", value: "" },
{ name: "订货量", key: "netWeight", value: "" },
],
gp: ["orderThick","width","orderLength","netWeight" ],
rz: ["coilInnerDia","orderThick","width","orderLength","netWeight" ],
zhb: ["orderThick","width","orderLength","netWeight" ],
rzsx: ["coilInnerDia","orderThick","width","orderLength","netWeight" ],
lz: ["coilInnerDia","orderThick","width","orderLength","netWeight" ],
rdx: ["coilInnerDia","orderThick","width","orderLength","netWeight" ],
wqx: ["coilInnerDia","orderThick","width","netWeight" ],
qx: ["coilInnerDia","orderThick","width","netWeight" ],
gg: ["orderLength","netWeight" ],
bc: ["orderThick","orderLength","netWeight" ],
ct: ["coilInnerDia","orderThick","width","netWeight" ],
xg: ["orderLength","netWeight" ],
wfgg: ["coilInsideDia","thickTbthDim","coilInnerDia","orderLength","netWeight" ],
xc: ["orderThick","netWeight" ],
};
},
mounted() {
this.setCategory();
this.setValue();
},
methods: {
setCategory() {
const item = this.item
let arr = [];
this.category.map((i, index) => {
Object.keys(item).includes(i.key) ? arr.push(this.category[index]) : "";
});
this.productType = arr;
},
setValue() {
let arr = this.productType
const mapping = {
A: "gp",
B: "rz",
C: "zhb",
D: "rzsx",
E: "lz",
G: "rdx",
N: "wqx",
Q: "qx",
R: "gg",
S: "bc",
T: "ct",
U: "xg",
W: "wfgg",
X: "xc",
};
// const arr = this[mapping[this.categoryCode]];
if(arr.length){
arr.map((i) => {
i.value = this.item[i.key];
});

}
},
},
};
</script>

<style>
.input-box {
border: 1px solid #eee;
border-radius: 10rpx;
padding: 0 20rpx;
}
.weight-box {
width: 50vw;
}
</style>

相对于 Vue2.0 的 optionsAPI,Vue3.0 使用 compositionAPI.

setup

接收propscontext的函数.

setup避免使用this,因为它会找不到组件实例.setup的调用发生在data,computed, methods被解析之前,在setup中无法获取.

1
2
3
4
5
6
7
8
9
10
11
12
13
export default {
components: { a, b, c },
props: {
user: {
type: String,
required: true,
},
},
setup(props) {
console.log(props); // {user: ''}
return {};
},
};

第一个参数 props

props 是响应式的,所以不能使用 ES6 解构.它会消除 prop 的响应性.
如果非要解构 props,可以在setup函数中使用toRefs函数.

1
2
3
4
5
6
import { toRefs } from 'vue'
setup(props) {
const { title } = toRefs(props)

console.log(title.value)
}

可选 props

如果title是可选 prop, 则传入的props中可能没有title.则toRefs将不会为title创建一个 ref.
需要使用toRef替代.

1
2
3
4
5
import { toRef } from 'vue'
setup(props){
const title = toRef(props, 'title')
console.log(title.value)
}

第二个参数 Context

context 暴露组件的三个属性;

1
2
3
4
5
6
7
8
9
10
11
export default {
setup(props, context){
console.log(context.attrs)

console.log(context.slots)

console.log(context.emit)
}
}
//context不是响应式的所以可以使用解构
setup(props,{attrs, slots, emit}){}

模板中不再使用.value,因为访问时是被自动浅解包的.

使用渲染函数

1
2
3
4
5
6
7
8
9
10
import { h, ref, reactive ) from 'vue'

export default {
setup(){
const readersNumber = ref[0]
const book = reactive({title: "Vue3 Guide"})

return () => h('div', [readersNumber.value, book.title])
}
}

使用 this

在 setup() 内部,this 不是该活跃实例的引用,因为 setup() 是在解析其它组件选项之前被调用的,所以 setup() 内部的 this 的行为与其它选项中的 this 完全不同。这使得 setup() 在和其它选项式 API 一起使用时可能会导致混淆。

toRefs

可以把基本类型的数据变为响应式的,适用于对象解构后的处理。
可以把响应式数据内的所有属性都转化为响应式的。

reactive

可以把

ref的响应式变量

在 3.0 中,可以通过ref函数使任何响应式变量在任何地方起作用.
ref接收参数并将其包裹在一个带有value属性的对象中返回,然后可以使用该属性访问或更改响应式变量的值.

1
2
3
4
5
6
7
8
9
import { ref } from "vue";

const counter = ref(0);

console.log(counter); // {value: 0}
console.log(counter.value); //0

counter.value++;
console.log(counter.value); //1

生命周期钩子

setup中,注册生命周期钩子.与 optionsAPI 名称相同,前缀为on,即mountedonMounted.
beforeCreatecreated直接在setup中.其他的生命周期加上on.
另外需要 import 引入.
没有beforeCreatecreated是因为setup是围绕他们运行的不需要显示的定义他们.

watch 响应式更改

接收三个参数,一个响应式引用或 getter 函数,一个回调,可选的配置选项.

1
2
3
4
5
6
import { ref, watch } from "vue";

const counter = ref[0];
watch(counter, (newValue, oldValue) => {
console.log("new value:" + counter.value);
});

每当 counter 被修改,侦听将触发并执行回调(第二个参数).
watch 是惰性执行,只有发生改变才会执行,想要立即执行,第三个参数immediate: true.

1
2
3
4
5
6
7
8
9
<!-- 监听单个ref定义的响应式数据 -->
setup(){ let mes = ref("hello") watch(mes, (old, new) => {}, {immediate: true})
} // 监听多个ref setup(){ let mes1 = ref("hello") let mes2 = ref("world")
watch([mes1,mes2], (old,new)=>{},{immediate: true}) } //
监听reactive定义的属性(基本数据),默认开启deep:true setup(){ let res = reactive({
name: 'Tom', obj: { foo: 10 } }) // 监听name watch(() => res.name, (old,new) =>
{}) } // 监听reactive定义的引用数据,需要自己开启deep:true setup(){ //
沿用上面的res watch([() => res.name, () => res.obj], (old,new) => {}, {deep:
true}) }

watchEffect

立即执行传入的回调,并响应式追踪其依赖,依赖改变时重新执行回调.

1
2
setup(){ const id = ref(0) watchEffect(() => { console.log(id) }) setTimeout(()
=> { id.value = 1 },1000) }

停止监听

watchEffect 会在组件卸载时自动停止,显示的调用返回值会立即停止监听

1
const stop = watchEffect(()=>{}) stop()

清除副作用

使用onInvalidate函数作为参数,用来清除副作用.
触发时机:

  • 副作用即将重新执行
  • 停止监听
1
2
3
4
import { watchEffect, ref } from 'vue' const count = ref(0)
watchEffect((onInvalidate) => { console.log(count.value) onInvalidate(() => {
console.log('执行了onInvalidate') }) }) setTimeout(()=> { count.value++ }, 1000)
// 0 => 执行了onInvalidate => 1

分析:初始化时先打印 count 的值 0, 然后由于定时器把 count 的值更新为 1, 此时副作用即将重新执行,因此 onInvalidate 的回调函数会被触发,打印执行了 onInvalidate,然后执行了副作用函数,打印 count 的值 1。

1
2
3
import { watchEffect, ref } from 'vue' const count = ref(0) const stop =
watchEffect((onInvalidate) => { console.log(count.value) onInvalidate(() => {
console.log('执行了onInvalidate') }) }) setTimeout(()=> { stop() }, 1000)

上述代码:当我们显示执行 stop 函数停止侦听,此时也会触发 onInvalidate 的回调函数。同样,watchEffect 所在的组件被卸载时会隐式调用 stop 函数停止侦听,故也能触发 onInvalidate 的回调函数。

watchEffect 的应用

场景一:
定时器的注册和清除; DOM 事件的监听和清除(DOM 事件只能在挂载后,onMounted)

1
2
3
4
5
6
7
watchEffect((onInvalidate) => { let timer = setInterval(()=>{},1000)
onInvalidate(()=> clearInterval(timer)) }) // dom事件的监听 onMounted(() => {
watchEffect((onOnvalidate) => {
document.getElementById('.btn').addEventListener('click',function(){})
onInvalidate(()
=>document.getElementById('.btn').removeEventListener('click',function(){}) })
})

场景二:
利用 watchEffect 做防抖节流

1
2
3
const id = ref(1) watchEffect((onInvalidate) => { // 异步请求 const token =
await fetch('...',id) onInvalidate(() => { // id变化频繁就取消请求
token.cancel() }) })

watch 和 watchEffect 的区别

  1. watch 可以访问新旧值,watchEffect 不可以
  2. watchEffect 有副作用,DOM 挂载或者更新前会触发,需要手动清除
  3. watch 是惰性执行,只有监听值发生改变才会执行,watchEffect 每次代码加载都会执行
  4. watch 需要指明监听的对象,也需要监听的回调.watchEffect 不用指明,监听的回调中用到那个属性就监听哪个.

独立的computed属性

1
2
3
4
5
6
7
8
import { ref, computed } from "vue";

const counter = ref[0];
const twiceTheCounter = computed(() => counter.value * 2);

counter.value++;
console.log(counter.value); //1
console.log(twiceTheCounter.value); //2

新创建的计算变量的 twiceTheCounter 也需要使用.value的方式调用.

Provide/Inject

使用 provide

provide 有两个参数,name(类型)和 value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//Vue2
<template>
<MyMarker />
</template>
<script>
import MyMarker from './MyMarker.vue'
export default {
components: {
MyMarker
},
provide: {
location: 'North Pole',
geolocation: {
longitude: 90,
latitude: 135
}
}
}
</script>
//MyMarker.vue
export default {
inject: ['location', 'geolocation']
}

//Vue3重构
<script>
import { provide } from 'vue'
import MyMarker from './MyMarker.vue'

export default {
components: {
MyMarker
},
setup(){
provide('location','North Pole')
provide('geolocation', {
longitude: 90,
latitude: 135
})
}
}

使用 inject

inject 有两个参数,一个是要注入的属性 name,一个是默认值(可选)

1
2
3
4
5
6
7
8
9
10
11
12
import { inject } from "vue";
export default {
setup() {
const userLocation = inject("location", "The Universe");
const userGeolocation = inject("geolocation");

return {
userLocation,
userGeolocation,
};
},
};

响应性

更新 inject 的数据,建议 provide 一个方法来改变响应式属性.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<MyMarker />
</template>
<script>
import { provide, reactive, ref } from 'vue'
import MyMarker from './MyMarker.vue'
export default {
components: {
MyMarker
},
setup(){
const location = ref('North Pole')
const geolacation = reactive({
longitude: 90,
latitude: 135
})
const updateLocation = () => {
location.value = 'South Pole'
}
provide('location', location)
provide('geolocation', geolocation)
//+
provide('updateLocation', updateLocation)
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//MyMarker.vue
<script>
import { inject } from 'vue'
export default {
setup(){
const userLocation = inject('location','The Universe')
const userGeolocation = inject('geolocation')
const updateUserLocation = inject('updateLocation')

return {
userLocation,
userGeolocation,
updateUserLocation
}
}
}
</script>

如果确保通过provide传递的数据不会被inject的组件更改,我们建议对提供者的属性使用readonly.

1
2
3
4
5
6
import { provide, reactive, readonly, ref } from 'vue'
...

provide('location',readonly(location))
provide('geolocation',readonly(geolocation))
provide('updateLocation', updateLocation)

模板引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div ref="root">this is a root element</div>
</tempalte>

<script>
import {ref, onMounted} from 'vue'
export default {
setup(){
const root = ref(null)

onMounted(() => {
console.log(root.value)
})
return {
root
}
}
}
</script>

在渲染上下文暴露root,并通过ref="root",将其绑定到 div 作为其 ref.在虚拟 DOM 补丁算法中,如果 VNode 的ref键对应于渲染上下文的 ref,则 VNode 的相应元素或实例将被分配给 ref 的值.这是在虚拟 DOM 挂载/打补丁过程中执行的,因此模板引用只会在初始渲染之后获得赋值.

JSX 中的用法

1
2
3
4
5
6
7
8
9
10
11
12
export default {
setup() {
const root = ref(null);

return () =>
h("div", {
ref: root,
});
//with JSX
return () => <div ref={root} />;
},
};

侦听模板引用

因为 watch()和 watchEffect()在 DOM 挂载或更新之前运行副作用,所以当侦听器运行时,模板引用还未被更新.
所以,使用模板引用的侦听器应该用{flush: 'post'}来定义,这将在 DOM 更新后运行副作用,

1
2
3
4
5
6
7
8
9
10
setup(){
const root = ref[null]
watchEffect(()=> {
console.log(root.value)
},
{
flush: 'post'
})
return {root}
}

vue3.0+ts+vite
赏金猎人
通缉令爬虫
发布寻人

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
<template>
<div id="app">
<ul class="shop">
<li v-for="(item,index) in items" :key="index">
<span>{{item.text}}</span>
<span>{{item.price}}</span>
<button @click="additem">添加</button>
</li>
</ul>
<div class="cart" style="">{{count}}</div>
<div class="ball-container"><!--小球-->
<div v-for="(ball,index) in balls" :key="index">
<transition name="drop"
@before-enter="beforeDrop"
@enter="dropping"
@after-enter="afterDrop">
<div class="ball" v-show="ball.show">
<div class="inner inner-hook"></div>
</div>
</transition>
</div>
</div>
</div>
</template>

<script>
export default {
data(){
return{
count: 0,
items: [
{
text: "苹果",
price: 15
},
{
text: "香蕉",
price: 15
}
],
balls: [ //小球 设为3个
{
show: false
},
{
show: false
},
{
show: false
},
],
dropBalls:[],
}
},
methods:{
additem(event){
this.drop(event.target);
this.count ++;
},
drop(el){ //抛物
for(let i=0;i<this.balls.length;i++){
let ball= this.balls[i];
if(!ball.show){
ball.show = true;
ball.el=el;
this.dropBalls.push(ball);
return;
}
}
},
beforeDrop(el) {/* 购物车小球动画实现 */
let count = this.balls.length;
while (count--) {
let ball = this.balls[count];
if (ball.show) {
let rect = ball.el.getBoundingClientRect(); //元素相对于视口的位置
let x = rect.left - 32;
let y = -(window.innerHeight - rect.top - 22); //获取y
el.style.display = '';
el.style.webkitTransform = 'translateY('+y+'px)'; //translateY
el.style.transform = 'translateY('+y+'px)';
let inner = el.getElementsByClassName('inner-hook')[0];
inner.style.webkitTransform = 'translateX('+x+'px)';
inner.style.transform = 'translateX('+x+'px)';
}
}
},
dropping(el, done) { /*重置小球数量 样式重置*/
let rf = el.offsetHeight;
el.style.webkitTransform = 'translate3d(0,0,0)';
el.style.transform = 'translate3d(0,0,0)';
let inner = el.getElementsByClassName('inner-hook')[0];
inner.style.webkitTransform = 'translate3d(0,0,0)';
inner.style.transform = 'translate3d(0,0,0)';
el.addEventListener('transitionend', done);
},
afterDrop(el) { /*初始化小球*/
let ball = this.dropBalls.shift();
if (ball) {
ball.show=false;
el.style.display = 'none';
}
}
}
}
</script>

<style>
.shop{
position: fixed;
top: 300px;
left: 40px;
}
.ball{
position: fixed;
left: 32px;
bottom: 22px;
z-index: 200;
transition: all 0.4s cubic-bezier(0.49, -0.29, 0.75, 0.41); /*贝塞尔曲线*/
}
.inner{
width: 16px;
height: 16px;
border-radius: 50%;
background-color: rgb(0,160,220);
transition: all 0.4s linear;
}
.cart{
position: fixed;
bottom: 22px;
left: 32px;
width: 30px;
height: 30px;
background-color: rgb(0,160,220);
color: rgb(255,255,255);
}

</style>

其他详细说明:

https://blog.csdn.net/tangxiujiang/article/details/80200865

捕获路由

匹配参数

1
2
3
4
//匹配所有参数
{ path: "*" }
//匹配`/user-`开头的任意路径
{ path: "/user-" }

含有通配符的路由通常放在最后,{path:"*"}通常用于 404 页面.

路由后缀

当使用通配符时,$route.params内会自动添加一个名为pathMatch.包含了 URL 通过通配符被匹配的部分:

1
2
3
//给出一个路由 { path: "/user-*"}
this.$router.push("/user-admin");
this.$route.params.pathMatch; //'admin'

路由跳转

router.push

声明式: <router-link :to="...">
编程式: router.push(...)

1
2
3
4
5
6
7
8
//字符串
router.push("home");
//对象
router.push({ path: "home" });
//命名的路由
router.push({ name: "user", params: { userId: "123" } });
//带查询参数,变成/register?plan=private
router.push({ path: "register", query: { plan: "parivate" } });

注意nameparams搭配,pathquery搭配

1
2
3
4
5
const userId = '123'
router.push({name:"user", params:{ userId }) //=> /user/123
router.push({path: `/user/${userId}`}) //=> /user/123
//这里的params不生效
router.push({ path: 'user', params: { userId }}) //=>/user

router.replace

此方法和 router.push 很像,不同的是不会向 history 添加新记录,而是替换掉当前的 history 记录.
声明式: <router-link :to="..." replace>
编程式: router.push(...)

router.go

此方法参数是整数,类似window.history.go()

和 window 的方法类似

router.push => window.history.pushState
router.replace => window.history.replaceState
router.go => window.history.go

路由命名

通过name给路由命名

1
2
3
4
5
//跳转调用
<router-link :to="{name: 'user',params: { userId: 123 } }>User</router-link>
//编程式写法
router.push({name: 'user', params: {userId:123}})

重定向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const router = new VueRouter({
routes: [
{path: '/a', redirect: '/b' }
]
})
//重定向到命名路由
const router = new VueRouter({
routes: [
{path: '/a', redirect: { name: 'foo'} }
]
})
//方法
const router = new VueRouter({
routes: [
{path: '/a', redirect: to => {
//方法接收 目标路由作为参数
//return 重定向的 字符串路径/路径对象
}
]
})

别名

区别:
重定向: 用户访问/a,URL 被替换为/b,匹配路由为 /b
别名: /a的别名是/b.用户访问/b时,URL 会保持为/b,但是路由匹配是/a.就像访问了/a一样.

1
2
3
const router = new VueRouter({
routes: [{ path: "/a", component: A, alias: "/b" }],
});

路由传参

通过在路由 routes 中设置 props 进行解耦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const User = {
props: ["id"],
template: "<div>User {{ id }}</div>",
};
const router = new VueRouter({
routes: [
{ path: "/user/:id", component: User, props: true },

// 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
{
path: "/user/:id",
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false },
},
],
});

如果 props 为 true,就可以通过组件获取参数.

history 模式

除了在 new Router()中设置mode: history外,还需要后端设置.

路由钩子(导航守卫,路由守卫)

beforeEach

可以设置进度条,
重置 store

1
2
store.commit("SET_TABS", []);
store.dispatch("app/goToTop", true);

判断是否有不需要登录就可以看的页面
如果需要跳转到登录
如果已登录,操作一些需要做的

1
2
3
4
5
//未登录不能验证身份
router.beforeEach((to, from, next) => {
if (to.name !== "Login" && !isAuthenticated) next({ name: "Login" });
else next();
});

路由独享守卫

在路由配置上的beforeEnter守卫:

1
2
3
4
5
6
7
8
9
10
11
const router = new VueRouter({
routes: [
{
path: "/foo",
component: Foo,
beforeEnter: (to, from, next) => {
//...
},
},
],
});

路由元信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const router = new VueRouter({
routes: [
{
path: "/foo",
component: Foo,
children: [
{
path: "bar",
component: Bar,
// a meta field
meta: { requiresAuth: true },
},
],
},
],
});

一个路由匹配到的所有路由记录会暴露为$route对象(还有在导航守卫中的路由对象)的$route.matched数组.因此,我们需要遍历$route.matched来检查路由记录中的meta字段.

如果有的页面不用登录就可以访问,就可以在 meta 中设置字段.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
router.beforeEach((to, from, next) => {
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (!auth.loggedIn()) {
next({
path: "/login",
query: { redirect: to.fullPath },
});
} else {
next();
}
} else {
next(); //确保一定要调用 next()
}
});

路由守卫

分为三种,全局,单个路由独享,组件的.

全局

相当于程序的页面生命周期.
全局守卫 3 个,beforeEach, beforeResolve, afterEach
分别表示进入路由前,解析前,离开路由时.
beforeEach: 路由跳转前触发,参数to,form,next,主要用于登录验证.
beforeResolve: 和 beforeEach 类似,在 beforeEach 和 beforeRouteEnter 之后,afterEach 之前.
afterEach: 在路由跳转后触发.参数to,from.

next()函数的作用是回调验证导航.
next(): 什么都不传,表示继续执行.多个 next()则可能不会解析或报错.
next(false): 中断当前导航.
next(‘/‘): 跳转到该地址.
next(error): 钩子中止将错误传递给 router.onerror()回调.

1
2
const router = new VueRouter();
router.beforeEach((to, from, next) => {});

路由独享守卫

beforeEnter: 只在进入路由时触发.不会在params,query,hash改变时触发.

1
2
3
4
5
6
7
8
9
const router = new VueRouter({
routes: [
{
path: "./foo",
component: Foo,
beforeEnter: (to, from, next) => {},
},
],
});

组件内守卫

有三个,beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave.
分别代表,渲染该组件对应的路由被验证前调用,路由复用同一个组件,离开当前路由

1
2
3
4
5
6
7
8
9
const Foo = {
template: "...",
beforeRouteEnter(to, from) {},
beforeRouteUpdate(to, from) {
// 比如动态参数/users/:id/,在user/1和user/2之间切换,那就复用同一个组件,
// 就会被调用,而且可以使用this,其他两个不行,因为组件没有了
},
beforeRouteLeave(to, from) {},
};

滚动行为

切换到新路由时,滚动到顶部或者保持原来的位置,可以设置滚动行为.

此功能只在支持history.pushState的浏览器可用

1
2
3
4
5
6
const router = new VueRouter({
routes: [...],
scrollBehavior(to, from, savedPosition) {
return {x:0, y: 0}
}
})

scrollBehavior方法接收tofrom路由对象.第三个参数savedPosition当且仅当popstate导航(浏览器的前进后退按钮触发)时才可用.

1
2
3
4
5
6
7
scrollBehavior(to, form, savedPosition){
if(savedPosition) {
return savedPosition
}else{
return {x:0,y:0}
}
}

滚动到锚点

1
2
3
4
5
scrollBehavior(to, form, savedPosition){
if(to.hash) {
return to.hash
}
}

平滑滚动

1
2
3
4
5
6
7
scrollBehavior(to, form, savedPosition){
if(to.hash) {
return {
selector: to.hash,
behavior: 'smooth'
}
}

路由懒加载

1
2
3
4
5
6
{
path: '/bbsHome',
name: 'BbsHome',
component: () => import(/* webpackChunkName: "portal" */ '@/views/bbs/bbsHome.vue'),
meta: { title: '论坛首页', auth: 'no' }
},

常见问题

  1. 两个页面参数不同使用同一组件,默认情况下当这两个页面切换时并不会触发 created 或者 mounted 钩子。
  • 通过watch$route 的变化处理
1
2
3
4
5
6
7
watch: {
$route() {
if($route) {
...
}
}
}
  • 通过在<route-view>上加唯一的key保证渲染.
1
<router-view :key='$route.fullPath'></router-view>

刷新当前页面

1
2
3
4
this.$router.go(0);
location.reload();
//这个只刷数据
this.$forceUpdate;

跳转到其他页面

1
2
3
4
//直接跳转,可以用后退返回
window.location.href = url;
//打开新的窗口
window.open(url);

模态框 Modal

如果有表格校验,不要用on-ok.使用普通的button绑定事件.

表单校验

注意ref,rules,所校验的值类型.