Vue2
一、Vue核心
01_简介
1.特点
- 采用组件化模式,提高代码复用率、且让代码更好维护。
- 声明式编码,让编程人员无需直接操作DOM(命令式编码),提高开发效率。
- 使用虚拟DOM+优秀的Diff算法,尽量复用DOM节点。
2.预备知识
- ES6语法规范
- ES6模块化
- 包管理器(npm、yarn……
- 原型、原型链【重要】
- 数组常用方法
- axios
- promise
- ……
3.官方文档
02_Hello入门案例
shift + F5
:强制页面刷新
// 阻止vue在启动时生成生产提示
Vue.config.productionTip = false;
// 创建Vue实例
new Vue({
el: "#root", //用于指定当前Vue实例为那个容器服务,值通常为css选择器字符串,也可以使用document.getElment...来获取容器
data: {
//data中用于存储数据,数据供el所指定的容器去使用
msg: "ywj",
},
});
初识Vue:
1.想让Vue工作,就必须创建一个Vue实例(new
),且要传入一个配置对象;
2.root容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法;
3.root容器里的代码被称为【Vue模板】;
4.Vue实例和容器是一一对应的;
5.真实开发中只有一个Vue实例,并且会配合着组件一起使用;
6.{{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性;
7.一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新;
注意区分:js表达式 和 js代码(语句)
-
表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方:
(1). a
(2). a+b
(3). demo(1)
(4). x === y ? ‘a’ : ‘b’
-
js代码(语句)
(1). if(){}
(2). for(){}
03_模板语法
Vue模板语法有2大类:
1.插值语法:
功能:用于解析标签体内容。
写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性。
2.指令语法:
功能:用于解析标签(包括:标签属性、标签体内容、绑定事件…)。
举例:v-bind:href=“xxx” 或 简写为 :href=“xxx”,xxx同样要写js表达式,且可以直接读取到data中的所有属性。
备注:Vue中有很多的指令,且形式都是:v-???,此处我们只是拿v-bind举个例子。
04_数据绑定
Vue中有2种数据绑定的方式:
1.单向绑定(v-bind
):
数据只能从data流向页面。【缩写:
】
2.双向绑定(v-model
):
数据不仅能从data流向页面,还可以从页面流向data。
v-model只能应用在表单类元素(输入类元素)上
备注:
1.双向绑定一般都应用在表单类元素上(如:input、select等)
2.
v-model:value
可以简写为 v-model,因为v-model默认收集的就是value值。
05_补充 el与data的两种写法
1.el有2种写法
(1).new Vue时候配置el属性
需要在创建Vue的时候就想好绑定在哪一个容器上
(2).先创建Vue实例,随后再通过vm.$mount('#root')
指定el的值。
2.data有2种写法
(1).对象式
new Vue({
el:'#root',
data:{
//data数据
}
})
(2).函数式
data:function(){ // 不能够写成箭头函数,因为箭头函数没有this
// 可以省略成 data(){}
// this指向Vue实例对象
return{
//data数据
}
}
如何选择:目前哪种写法都可以,以后学习到组件时,data必须使用函数式,否则会报错。
3.一个重要的原则:
由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了。
06_MVVM模型
1.MVVM模型
-
M:模型(Model) :data中的数据
-
V:视图(View) :模板代码
-
VM:视图模型(ViewModel):Vue实例
2.观察发现:
- data 中所有的属性,最后都出现在了vm身上。
- vm 身上所有的属性 及 Vue原型上所有属性,在 Vue 模板中都可以直接使用。
07_数据代理
🚩1.Object.defineProperty()
defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象 和 get 读取/ set 修改
应当直接在
Object
构造器对象上调用此方法,而不是在任意一个Object
类型的实例上调用。
语法:
Object.defineProperty(obj, prop, descriptor)
参数:
- 要定义属性的对象
- 要定义或修改的属性的名称或
Symbol
- 要定义或修改的属性描述符
返回值:
被传递给函数的对象
描述
该方法允许精确地添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的,在枚举对象属性时会被枚举到(for...in
或 Object.keys
方法),可以改变这些属性的值,也可以删除
这些属性。这个方法允许修改默认的额外选项(或配置)。
默认情况下,使用 Object.defineProperty()
添加的属性值是不可修改(immutable)的。
对象里存在的属性描述符主要有两种形式:【一个描述符只能是两者之一,不能同时是两者】
-
数据描述符
具有值得属性,该值可以是(不)可写得。
-
存取描述符
由
getter
函数和setter
函数所描述的属性。这两种描述符都是对象。它们共享以下可选键值:
可选键值 描述 configurable
仅当该属性键值为 true
时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。【默认为false
】enumerable
仅当该属性的 enumerable
键值为true
时,该属性才会出现在对象的枚举属性中。【默认为false
】value
1该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。【默认为 undefined
】writable
1当且仅当该属性的 writable
键值为true
时,属性的值,也就是上面的value
,才能被赋值运算符
(en-US)改变。【默认为false
】get
2属性的 getter 函数,如果没有 getter,则为 undefined
。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入this
对象(由于继承关系,这里的this
并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。【默认为undefined
】set
2属性的 setter 函数,如果没有 setter,则为 undefined
。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的this
对象。【默认为undefined
**】
let number = 18
let person = {
name:'张三',
sex:'男',
}
Object.defineProperty(person,'age',{
// value:18,
// enumerable:true, //控制属性是否可以枚举,默认值是false
// writable:true, //控制属性是否可以被修改,默认值是false
// configurable:true //控制属性是否可以被删除,默认值是false
//当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
get(){
console.log('有人读取age属性了')
return number
},
//当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
set(value){
console.log('有人修改了age属性,且值是',value)
number = value
}
})
描述符默认值汇总
- 拥有布尔值的键
configurable
、enumerable
和writable
的默认值都是false
。 - 属性值和函数的键
value
、get
和set
字段的默认值为undefined
。
描述符可拥有的键值
configurable | enumerable | value | writable | get | set | |
---|---|---|---|---|---|---|
数据描述符(实值) | 可以 | 可以 | 可以 | 可以 | 不可以 | 不可以 |
存取描述符(getter、setter) | 可以 | 可以 | 不可以 | 不可以 | 可以 | 可以 |
如果一个描述符不具有
value
、writable
、get
和set
中的任意一个键,那么它将被认为是一个数据描述符。如果一个描述符同时拥有value
或writable
和get
或set
键,则会产生一个异常。
2.📌Vue中的数据代理
数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)
-
Vue中的数据代理:
通过vm对象来代理data对象中属性的操作(读/写)
-
Vue中数据代理的好处:
更加方便的操作data中的数据
-
基本原理:
通过
Object.defineProperty()
把data对象中所有属性添加到vm上。为每一个添加到vm上的属性,都指定一个getter/setter。
在getter/setter内部去操作(读/写)data中对应的属性。
08_事件处理
1.事件的基本使用:
1.使用v-on:xxx
或 @xxx
绑定事件,其中xxx是事件名;
2.事件的回调需要配置在methods
对象中,最终会在vm
上;
3.methods
中配置的函数,不要用箭头函数!否则this
就不是vm
了;
4.methods
中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象;
5.@click=“demo” 和 @click=“demo**($event)**” 效果一致,但后者可以传参;
注意:
- methods中配置的函数,使用普通函数
this
指向Vue实例,使用箭头函数,因为箭头函数没有单独的this
,只会从自己的作用域链的上一层继承this
,即this
指向的是window
,因此在methods
中不能使用箭头函数。$event
代表触发事件参数event。
2.事件修饰符:
-
📌
prevent
:阻止默认事件(常用);【e.preventDefault()
】 -
📌
stop
:阻止事件冒泡(常用);【e.stopPropagation()
/e.cancelBubble = true
】 -
📌
once
:事件只触发一次(常用); -
capture
:使用事件的捕获模式; -
self
:只有event.target
是当前操作的元素时才触发事件; -
passive
:事件的默认行为立即执行,无需等待事件回调执行完毕;【防止回调事件过于复杂导致默认行为卡顿、无效,一般在移动端更需要使用】
可以连写多个修饰符,对一个事件进行多重修饰,修饰顺序与写的顺序有关。
事件冒泡:所谓的冒泡指的就是事件的向上传导,当后代元素上的事件被触发时,其祖先元素的相同事件也会被触发
捕获模式:事件的传播有三个阶段,分别是捕获阶段(外层到内层)、目标阶段(目标元素)、冒泡阶段(内层到外层),一般默认是在冒泡阶段才开始处理事件。
scroll
事件:观察目标是滚动条,只要滚动条有变化就会触发,滚到底了再往下滚动不会触发。但是这个
scroll
有个小问题,当进入页面或者用户刷新页面的时候,滚动条不在最上面,一开始就会执行回调函数
wheel
事件:观察目标是鼠标的滚轮,拖拽滚动条、使用键盘上下滚动滚动条都不会触发,滚到滚动条底部了继续滚动鼠标滚轮仍会触发。事件默认情况下是,首先执行回调函数中的内容,再执行事件的默认行为(如滚动条移动)。【不是所有的事件都是,例如
srcoll
原本就是wheel.passive
后的情况】
3.键盘事件
直接@keyup/down.xxx(别名、keyCode)="方法名"
就可以使用
-
📌Vue中常用的按键别名:
-
回车 =>
enter
-
删除 =>
delete
(捕获“删除”和“退格”键) -
退出 =>
esc
-
空格 =>
space
-
换行 =>
tab
(特殊,必须配合keydown
去使用,因为tab本身的功能就是切换焦距目标) -
上 =>
up
-
下 =>
down
-
左 =>
left
-
右 =>
right
-
-
Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)
-
系统修饰键(用法特殊):
ctrl
、alt
、shift
、meta
(win
键)- 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。(快捷键)
- 配合keydown使用:正常触发事件。
可以连写多重修饰,例如
@keyup.ctrl.y="showInfo"
表示点击ctrl+y
触发事件showInfo
-
也可以使用
keyCode
去指定具体的按键**(不推荐,将来有可能废弃)** -
Vue.config.keyCodes.自定义键名 = 键码
,可以去定制按键别名(不太推荐,默认基本够用,还涉及keyCode
)
09_计算属性
-
定义:要用的属性不存在,要通过已有属性计算得来。
-
原理:底层借助了
Objcet.defineproperty
方法提供的getter和setter。 -
get函数什么时候执行?
(1).初次读取时会执行一次。
(2).当依赖的数据发生改变时会被再次调用。
-
优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
-
备注:
- 计算属性最终会出现在vm上,直接读取使用即可。(不需要去name.get这样使用)
- 如果计算属性要被修改,那必须写
set
函数去响应修改,且set中要引起计算时依赖的数据发生改变。
简写
只需要读取,不需要修改,就可以简写
computed:{
//完整写法
/* fullName:{
get(){
console.log('get被调用了')
return this.firstName + '-' + this.lastName
},
set(value){
console.log('set',value)
const arr = value.split('-')
this.firstName = arr[0]
this.lastName = arr[1]
}
} */
//简写
fullName(){
console.log('get被调用了')
return this.firstName + '-' + this.lastName
}
}
10_监视属性
1.监视属性
绑定事件的时候:@xxx=“yyy” yyy可以写一些简单的语句
-
当被监视的属性变化时,回调函数(
hander(newValue,oldValue)
)自动调用,进行相关操作【注意新旧值得顺序,变量名称无关紧要】 -
监视的属性必须存在(在
data
或者computed
中存在),才能进行监视!!(不存在也不会报错) -
监视的两种写法:
(1).new Vue时传入watch配置
(2).通过vm.$watch
监视(渐进式)
官网API文档:https://staging-cn.vuejs.org/api/options-state.html#watch
watch
watch
选项期望接受一个对象,其中键是需要侦听的响应式组件实例属性(例如,通过data
或computed
声明的属性)——值是相应的回调函数。该回调函数接受被侦听源的新值和旧值。除了一个根级属性,键名也可以是一个简单的由点分隔的路径,例如
a.b.c
。注意,这种用法不支持复杂表达式——仅支持由点分隔的路径。如果你需要侦听复杂的数据源,可以使用命令式的$watch()
API。值也可以是一个方法名称的字符串 (通过
methods
声明),或包含额外选项的对象。当使用对象语法时,回调函数应被声明在handler
中。额外的选项包含:
- 📌**
immediate
**:在侦听器创建时立即触发回调。第一次调用时,旧值将为undefined
。- 📌**
deep
**:如果源是对象或数组,则强制深度遍历源,以便在深度变更时触发回调。详见深层侦听器。flush
:调整回调的刷新时机。详见回调的触发时机。onTrack / onTrigger
:调试侦听器的依赖关系。详见侦听器调试。声明侦听器回调时避免使用箭头函数,因为它们将无法通过
this
访问组件实例。$watch
第一个参数是侦听来源。可以是一个组件的属性名的字符串,一个简单的由点分隔的路径字符串,或是一个 getter 函数。
第二个参数是📌回调函数。它接收的参数分别是侦听来源的新值、旧值。
2.深度监视
(1) .Vue中的watch
默认不监测对象内部值的改变(一层)。
(2). 配置deep:true
可以监测对象内部值改变(多层)。
备注:
(1). Vue自身可以监测对象内部值的改变,但Vue提供的watch
默认不可以!
(2). 使用watch
时根据数据的具体结构,决定是否采用深度监视。
3.简写
当配置项只有hander()回调函数的时候才能简写,有其他选项(如immediate、deep)都不能简写。
注意:不要写箭头函数!会导致
this
指向部位Vue实例
watch:{
//正常写法
isHot:{
// immediate:true, //初始化时让handler调用一下
// deep:true,//深度监视
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
},
//简写
isHot(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue,this)
}
}
//正常写法 vm.$watch
vm.$watch('isHot',{
immediate:true, //初始化时让handler调用一下
deep:true,//深度监视
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
})
//简写
vm.$watch('isHot',(newValue,oldValue)=>{
console.log('isHot被修改了',newValue,oldValue,this)
})
4.和computed区别
-
computed
能完成的功能,watch
都可以完成。【优先使用计算属性】 -
watch
能完成的功能,computed
不一定能完成,例如:watch
可以进行异步操作。
两个重要的小原则:
-
所有被Vue管理的函数,最好写成普通函数,这样this的指向才是
vm
或 组件实例对象。 -
所有不被Vue所管理的函数(定时器的回调函数、
ajax
的回调函数等、Promise
的回调函数),最好写成箭头函数,这样this
的指向才是vm
或 组件实例对象。
11_class与style绑定
随机生成
[0, max)
整数:Math.floor(Math.random()*max)
Math.floor()
返回小于或等于一个给定数字的最大整数。【向下取整】
Math.random()
返回一个[0,1)之间的小数。
1.class样式
写法:class="xxx"
xxx可以是字符串、对象、数组。
-
字符串写法适用于:类名不确定,要动态获取。
-
对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
-
数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
2.style样式
-
:style="{fontSize: xxx}"
其中xxx是动态值。 -
:style="[a,b]"
其中a、b是样式对象。
<div id="root">
<!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
<div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br/><br/>
<!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
<div class="basic" :class="classArr">{{name}}</div> <br/><br/>
<!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
<div class="basic" :class="classObj">{{name}}</div> <br/><br/>
<!-- 绑定style样式--对象写法 -->
<div class="basic" :style="styleObj">{{name}}</div> <br/><br/>
<!-- 绑定style样式--数组写法 -->
<div class="basic" :style="styleArr">{{name}}</div>
</div>
const vm = new Vue({
el:'#root',
data:{
name:'尚硅谷',
mood:'normal',
classArr:['atguigu1','atguigu2','atguigu3'],
classObj:{
atguigu1:false,
atguigu2:false,
},
styleObj:{
fontSize: '40px',
color:'red',
},
styleObj2:{
backgroundColor:'orange'
},
styleArr:[
{
fontSize: '40px',
color:'blue',
},
{
backgroundColor:'gray'
}
]
},
methods: {
changeMood(){
const arr = ['happy','sad','normal']
const index = Math.floor(Math.random()*3)
this.mood = arr[index]
}
},
})
12_条件渲染
1.v-if
写法:
v-if="表达式"
v-else-if="表达式"
v-else="表达式"
适用于:切换频率较低的场景。
特点:不展示的DOM元素直接被移除。
注意:v-if
可以和:v-else-if
、v-else
一起使用,但要求结构不能被“打断”。(中间不能插入其他的元素节点)
2.v-show
写法:v-show="表达式"
适用于:切换频率较高的场景。
特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉(display: none;
)
3.备注:
使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到。
<template>
包裹不影响结构,可以包裹一部分代码,成组进行条件渲染
13_列表渲染
1.v-for指令
-
用于展示列表数据
-
语法:
v-for="(item, index) in xxx" :key="yyy"
【key
非常重要,采用单向绑定v-bind
】 -
可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
想要列表渲染有顺序,可以使用对象数组。
两个参数:第一个是值value,第二个是索引值。
如:数组:第一个是值value,第二个是index索引值
对象:第一个是值value,第二个是key键
字符串:第一个是字符char,第二个是index索引值
📌2.key原理
官方文档:
预期:
number | string | boolean (2.4.2 新增) | symbol (2.5.12 新增)
key
的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。
最常见的用例是结合
v-for
:<ul> <li v-for="item in items" :key="item.id">...</li> </ul>
它也可以用于强制替换元素/组件而不是重复使用它。当你遇到如下场景时它可能会很有用:
- 完整地触发组件的生命周期钩子
- 触发过渡
面试题:react、vue中的key有什么作用?(key的内部原理)
-
虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
-
对比规则:
(1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
(2).旧虚拟DOM中未找到与新虚拟DOM相同的key,创建新的真实DOM,随后渲染到到页面。 -
用index作为key可能会引发的问题:
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。 - 如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
-
开发中如何选择key?:
- 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
- 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
默认:不写出
key
时,Vue默认使用index
作为key值。
3.列表过滤
indexOf
:返回某个指定的字符串值在字符串中首次出现的位置。对大小写敏感!没有出现返回**-1**。注意一个字符串中indexOf(‘’)
结果是0,不是-1。
filter
:创建一个新数组,其包含通过所提供函数实现的测试的所有元素。var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])
参数
callback
用来测试数组的每个元素的函数。返回
true
表示该元素通过测试,保留该元素,false
则不保留。它接受以下三个参数:
element
数组中当前正在处理的元素。index
可选正在处理的元素在数组中的索引。array
可选调用了filter
的数组本身。
thisArg
可选执行
callback
时,用于this
的值。返回值
一个新的、由通过测试的元素组成的数组,如果没有任何数组元素通过测试,则返回空数组。
new Vue({
el: "#root",
data: {
keyWord: "",
persons: [
{ id: "001", name: "马冬梅", age: 19, sex: "女" },
{ id: "002", name: "周冬雨", age: 20, sex: "女" },
{ id: "003", name: "周杰伦", age: 21, sex: "男" },
{ id: "004", name: "温兆伦", age: 22, sex: "男" },
],
},
computed: { //使用计算属性实现
filPersons() {
return this.persons.filter((p) => {
return p.name.indexOf(this.keyWord) !== -1;
});
},
},
/* watch: { //使用监视属性实现
keyWord: {
immediate: true,
handler(val) {
this.filpersons = this.persons.filter((p) => {
return p.name.indexOf(val) !== -1;
});
},
},
}, */
});
<div id="root">
<h2>人员列表</h2>
<input type="text" placeholder="请输入名字" v-model="keyWord" />
<ul>
<li v-for="(p,index) of filPersons" :key="index">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
4.列表排序
sort
方法:前减后升序a-b,后减前降序b-a对数组的元素进行排序。会改变原始数组。
排序顺序可以是按字母或数字,也可以是升序(向上)或降序(向下)。【Unicode编码顺序】
默认情况下,
sort()
方法将按字母和升序将值作为字符串进行排序。语法:
array.sort(compareFunction)
参数可选。定义替代排序顺序的函数。该函数应返回负值、零值或正值,具体取决于参数
例如:
function(a, b){return a-b}
回调函数需要定义两个形参,浏览器会分别使用数组中的元素作为值发送给比较函数(使用哪个元素调用不确定,但是在数组中a肯定在b的前边
浏览器会根据回调函数的返回值来决定元素的顺序
- 如果返回一个大于0的值,则元素会交换位置
- 如果返回一个小于0的值,则元素位置不变
- 如果返回一个0,则认为两个元素相等,也不交换位置
new Vue({
el: "#root",
data: {
keyWord: "",
persons: [
{ id: "001", name: "马冬梅", age: 19, sex: "女" },
{ id: "002", name: "周冬雨", age: 25, sex: "女" },
{ id: "003", name: "周杰伦", age: 21, sex: "男" },
{ id: "004", name: "温兆伦", age: 22, sex: "男" },
],
sortType: 0, //0-原顺序 1-降序 2-升序
},
computed: {
filPersons() {
const arr = this.persons.filter((p) => {
return p.name.indexOf(this.keyWord) !== -1;
});
// 实现排序
if (this.sortType) {
return arr.sort((a, b) => {
return this.sortType == 1 ? b.age - a.age : a.age - b.age;
});
} else {
return arr;
}
},
},
});
<div id="root">
<h2>人员列表</h2>
<input type="text" placeholder="请输入名字" v-model="keyWord" />
<button @click="sortType = 2">年龄升序</button>
<button @click="sortType = 1">年龄降序</button>
<button @click="sortType = 0">原顺序</button>
<ul>
<li v-for="(p,index) of filPersons" :key="index">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
5.Vue检测数据的原理
对象
靠getter、setter监视数据的改变,发生改变后setter就去解析模板–>生成虚拟DOM–>diff算法对比……
【类似于设计模式种的观察者模式】Vue数据代理、递归查找所有的层次属性都设置对应的getter、setter
Vue.set的使用
Vue.set( target, propertyName/index, value )
-
参数:
{Object | Array} target
{string | number} propertyName/index
{any} value
-
返回值:设置的值。
-
用法:
向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如❗❗
this.myObject.newProperty = 'hi'
)注意对象不能是 Vue 实例(vm),或者 Vue 实例的根数据对象(_data或data)。
数组
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
-
push()
-
pop()
-
shift()
-
unshift()
-
splice()
-
sort()
-
reverse()
也可以使用
Vue.set
改变数组中内容
非变更方法,例如 filter()
、concat()
和 slice()
。它们不会变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组。
总结
Vue监视数据的原理:
通过setter实现监视,且要在new Vue时就传入要监测的数据。
(1).对象中后追加的属性,Vue默认不做响应式处理
(2).如需给后添加的属性做响应式,请使用如下API:
Vue.set(target,propertyName/index,value)
或
vm.$set(target,propertyName/index,value)
通过包裹数组更新元素的方法实现,本质就是做了两件事:
(1).调用原生对应的方法对数组进行更新。【更新数组】
(2).重新解析模板,进而更新页面。【更新页面】
-
在Vue修改数组中的某个元素一定要用如下方法:
- 使用这些API:
push()
、pop()
、shift()
、unshift()
、splice()
、sort()
、reverse()
Vue.set()
或vm.$set()
- 使用这些API:
特别注意:
Vue.set()
和vm.$set()
不能给vm
或vm
的根数据对象 添加属性!!!
数据劫持
vue2.0的数据劫持是利用Object.defineProperty()的getter和setter来监听到属性的变化时做一些事情。
而vue3.0 已经放弃了使用这个方法,而是采用 es6 提供的 proxy。
14_收集表单数据
- radio单选框需要自己亲手配置value,因为v-model默认获取的是value数据。
- checkbox多选框需要使用数组
[]
来接受数据,也需要设置value的值。
若:<input type="text"/>
,则v-model
收集的是value
值,用户输入的就是value
值。
若:<input type="radio"/>
,则v-model
收集的是value
值,且要给标签配置value
值。
若:<input type="checkbox"/>
-
没有配置
input
的value
属性,那么收集的就是checked
(勾选 or 未勾选,是布尔值) -
配置
input
的value
属性:
(1)v-model
的初始值是非数组,那么收集的就是checked
(勾选 or 未勾选,是布尔值)
(2)v-model
的初始值是数组,那么收集的的就是value
组成的数组
备注:v-model的三个修饰符:
lazy
:失去焦点再收集数据number
:输入字符串转为有效的数字trim
:输入首尾空格过滤
15_过滤器
1.定义:
对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
2.语法:
- 注册过滤器:
Vue.filter(name,callback)
【全局】或new Vue{filters:{}}
【局部】 - 使用过滤器:
{{ xxx | 过滤器名}}
【插值语法】或v-bind:属性 = "xxx | 过滤器名"
【v-bind,罕见】
3.备注:
-
过滤器也可以接收额外参数、多个过滤器也可以串联
-
并没有改变原本的数据, 是产生新的对应的数据
-
过滤器参数第一个默认为过滤器管道符前的数据,填写多个参数也只能是从第二个参数开始。
Vue3.0已删除,使用
proxy
代替。
- 插值语法中,使用函数取值
{{getTime()}}
一定需要()
16_内置指令
0.已经学过的指令:
v-bind
: 单向绑定解析表达式, 可简写为 :xxxv-model
: 双向数据绑定v-for
: 遍历数组/对象/字符串v-on
: 绑定事件监听, 可简写为@v-if
: 条件渲染(动态控制节点是否存存在)v-else
: 条件渲染(动态控制节点是否存存在)v-show
: 条件渲染 (动态控制节点是否展示)
1.v-text指令:
-
作用:向其所在的节点中渲染文本内容。
-
与插值语法的区别:
v-text
会替换掉节点中的内容,{{xx}}
则不会。
- 不支持解析标签结构,
v-html
可以- 不支持字符串拼接,都是全体代换
2.v-html指令:
-
作用:向指定节点中渲染包含html结构的内容。
-
与插值语法的区别:
(1). v-html
会替换掉节点中所有的内容,{{xx}}
则不会。
(2). v-html
可以识别html结构。
- 严重注意:
v-html
有安全性问题!!!!
(1).在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
(2).一定要在可信的内容上使用v-html
,永不要用在用户提交的内容上!
Cookie原理
3.v-cloak指令(没有值):
- 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉
v-cloak
属性。 - 使用css配合
v-cloak
可以解决网速慢时页面展示出{{xxx}}
的问题(未经解析的模板)。
这个指令保持在元素上直到关联实例结束编译。和 CSS 规则如 [v-cloak] { display: none }
一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。
[v-cloak] {
display: none;
}
<div v-cloak>
{{ message }}
</div>
JS阻塞
4.v-once:
v-once
所在节点在初次动态渲染后,就视为静态内容了。【只渲染元素和组件一次】- 以后数据的改变不会引起
v-once
所在结构的更新,可以用于优化性能。
5.v-pre:
- 跳过其所在节点的编译过程。
- 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
17_自定义指令
1.函数式
何时会被调用?
- 指令与元素成功绑定时(一开始)
- 指令所在的模板被重新解析时(注意不是依赖数据改变时,区分与computed)
指令钩子函数会被传入以下参数:【钩子函数参数】
el
:指令所绑定的元素,可以用来直接操作 DOM。binding
:一个对象,包含以下 property:name
:指令名,不包括v-
前缀。value
:指令的绑定值,例如:v-my-directive="1 + 1"
中,绑定值为2
。oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可用。expression
:字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
。arg
:传给指令的参数,可选。例如v-my-directive:foo
中,参数为"foo"
。modifiers
:一个包含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为{ foo: true, bar: true }
。
vnode
:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。oldVnode
:上一个虚拟节点,仅在update
和componentUpdated
钩子中可用。
除了
el
外,其他参数都应该是只读的。如果需要在钩子之间共享数据,建议通过元素的
dataset
来进行。
2.对象式
一个指令定义对象可以提供如下几个钩子函数 (均为可选):【钩子函数】
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。【用于添加focus()类方法,需要在元素存在时添加的方法】update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新。【指令所在的模板被重新解析时调用】componentUpdated
:指令所在组件的 VNode 及其子 VNode 全部更新后调用。unbind
:只调用一次,指令与元素解绑时调用。
HTMLElement.focus()
方法
将焦点设置在指定元素上,如果它可以被聚焦的话。
执行的时机,需要在添加到页面上之后,才能实现。
3.总结
(1).局部指令: 对象式 函数式
new Vue({ new Vue({
directives:{指令名:配置对象} 或 directives{指令名:回调函数}
}) })
(2).全局指令:对象式 函数式
Vue.directive(指令名,配置对象)
或 Vue.directive(指令名,回调函数)
(1).bind
:指令与元素成功绑定时调用。
(2).inserted
:指令所在元素被插入页面时调用。
(3).update
:指令所在模板结构被重新解析时调用。
指令是多个单词时,在directives里报错命名不正确,使用
‘big-number’
单引号将指令包裹起来,还原成vue原本的键值对中的键应当是字符串的形式,完整的写出指令定义即可。directives:{ 'big-number'(element, binding){ // xxxxx }, }
18_生命周期
1.基本概念
- 又名:生命周期回调函数、生命周期函数、生命周期钩子。
- 是Vue在关键时刻帮我们调用的一些特殊名称的函数。
- 生命周期函数的名字不可更改,但函数的具体内容可以由程序员按需编写。
- 生命周期函数中的
this
指向的是vm
或 组件实例对象。
Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
debugger
:断点调试代码
2.常用的生命周期钩子:
-
mounted
: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。 -
beforeDestroy
: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
3.关于销毁Vue实例
-
销毁后借助Vue开发者工具看不到任何信息。
-
销毁后自定义事件会失效,但原生DOM事件依然有效。
-
一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。
二、Vue组件化编程
传统方式编写应用
使用组件方式编写应用
01_模块与组件、模块化与组件化
1.模块
- 理解: 向外提供特定功能的 js 程序, 一般就是一个 js 文件
- 为什么: js 文件很多很复杂
- 作用: 复用 js, 简化 js 的编写, 提高 js 运行效率
2.组件
- 理解: 用来实现局部(特定)功能效果的代码集合(html/css/js/image……)
- 为什么: 一个界面的功能很复杂
- 作用: 复用编码, 简化项目编码, 提高运行效率
3.模块化
当应用中的 js 都以模块来编写的, 那这个应用就是一个模块化的应用。
4.组件化
当应用中的功能都是多组件的方式来编写的, 那这个应用就是一个组件化的应用。
02_非单文件组件
非单文件组件:一个文件中包含有n个组件
单文件组件:一个文件中只包含有1个组件【条理清晰,代码好维护】
0.Vue中使用组件的三大步骤:
- 定义组件(创建组件)
- 注册组件
- 使用组件(写组件标签)
1.定义组件
使用Vue.extend(options)
创建,其中options
和new Vue(options)
时传入的那个options几乎一样,但也有点区别;
el
不写 ——— 最终所有的组件都要经过一个vm
的管理,由vm
中的el
决定服务哪个容器。data
必须写成函数式 ———— 避免组件被复用时,数据存在引用关系。
备注:使用template
可以配置组件结构。
2.注册组件
- 局部注册:靠
new Vue
的时候传入components
选项 - 全局注册:靠
Vue.component('组件名',组件)
3.编写组件标签
<school></school>
4.注意点
1、组件名
一个单词组成:
- 第一种写法(首字母小写):
school
- 第二种写法(首字母大写):
School
多个单词组成:
- 第一种写法(kebab-case命名):
my-school
(注册时需要加上‘’
) - 第二种写法(CamelCase命名):
MySchool
(需要Vue脚手架支持)
备注:
- 组件名尽可能回避HTML中已有的元素名称,例如:
h2
、H2
都不行。 - 可以使用
name
配置项指定组件在开发者工具中呈现的名字。
2、组件标签
- 第一种写法:
<school></school>
- 第二种写法:
<school/>
【自闭合形式,需要Vue脚手架支持】
备注:不用使用脚手架时,<school/>
会导致后续组件不能渲染。
3、简写方式
const school = Vue.extend(options)
可简写为:const school = options
5.组件的嵌套
见代码文件
6.VueComponent构造函数
-
school
组件本质是一个名为VueComponent
的构造函数,且不是程序员定义的,是Vue.extend
生成的。 -
我们只需要写
<school/>
或<school></school>
,Vue解析时会帮我们创建school
组件的实例对象,即Vue帮我们执行的:new VueComponent(options)
。 -
特别注意:每次调用
Vue.extend
,返回的都是一个全新的VueComponent
!!!! -
关于
this
指向:-
组件配置中:
data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【
VueComponent
实例对象vc
】。 -
new Vue(options)
配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象
vm
】。
-
-
VueComponent
的实例对象,以后简称vc
(也可称之为:组件实例对象)。Vue的实例对象,以后简称
vm
。
组件是可复用的 Vue 实例,且带有一个名字
因为组件是可复用的 Vue 实例,所以它们与
new Vue
接收相同的选项,例如data
、computed
、watch
、methods
以及生命周期钩子等。仅有的例外是像el
这样根实例特有的选项。
7.一个重要的内置关系
VueComponent.prototype.__proto__ === Vue.prototype
目的:让组件实例对象(vc
)可以访问到 Vue原型上的属性、方法。
原型对象
function Demo(){ this.a = 1 this.b = 2 } //创建一个Demo的实例对象 const d = new Demo() console.log(Demo.prototype) //构造函数的显式原型属性 console.log(d.__proto__) //实例的隐式原型属性 console.log(Demo.prototype === d.__proto__) //true //程序员通过显示原型属性操作原型对象,追加一个x属性,值为99 Demo.prototype.x = 99 console.log('@',d.x) //@ 99
显式原型属性和隐式原型属性都指向同一个对象——原型对象
prototype
和__proto__
prototype
每个函数都有一个prototype属性,该属性是一个指针,指向一个对象(构造函数的原型对象) ,这个对象包含所有实例共享的属性和方法。原型对象都有一个
constructor
属性,这个属性指向所关联的构造函数。使用这个对象的好处就是可以让所有实例对象共享它所拥有的属性和方法。这个属性只用js中的类(或者说能够作为构造函数的对象)才会有。
它是函数所独有的,它是从一个函数指向一个对象。
它的含义是函数的原型对象,也就是这个函数所创建的实例的原型对象。
由此可知:
f1.__proto__ === Foo.prototype
它的作用就是包含可以由特定类型的所有实例共享的属性和方法,也就是让该函数所实例化的对象们都可以找到公用的属性和方法。任何函数在创建的时候,其实会默认同时创建该函数的prototype对象。
__proto__
每个实例对象都有一个proto属性,用于指向构造函数的原型对象(
prototype
)。proto属性是在调用构造函数创建实例对象时产生的。该属性存在于实例和构造函数的原型对象之间,而不是存在于实例与构造函数之间。
__proto__
和constructor
属性是对象所独有的;prototype
是函数所独有的(但在JS中函数也是一种对象
__proto__
属性都是由一个对象指向一个对象,即指向它们的原型对象(也可以理解为父对象)。它的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的
__proto__
属性所指向的那个对象(可以理解为父对象)里找以上这种通过
__proto__
属性来连接对象直到null
的一条链即为我们所谓的原型链。
[[Prototype]]
===__proto__
new操作符将函数作为构造器进行调用的过程:
函数被调用,然后新创建一个对象,并且成了函数的上下文(也就是此时函数内部的this是指向该新创建的对象,这意味着我们可以在构造器函数内部通过this参数初始化值),最后返回该新对象的引用,详细请看:详解JavaScript中的new操作符。
constructor
constructor
属性也是对象才拥有的,它是从一个对象指向一个函数,含义就是指向该对象的构造函数,每个对象都有构造函数(本身拥有或继承而来,继承而来的要结合__proto__
属性查看会更清楚点)所有函数和对象最终都是由Function构造函数得来,所以
constructor
属性的终点就是Function这个函数。每个对象都有构造函数
单从constructor这个属性来讲,只有prototype对象才有。
每个函数在创建的时候,JS会同时创建一个该函数对应的prototype对象,而
函数创建的对象.__proto__ === 该函数.prototype
,该函数.prototype.constructor===该函数本身
,故通过函数创建的对象即使自己没有constructor属性,它也能通过__proto__找到对应的constructor,所以任何对象最终都可以找到其构造函数(null如果当成对象的话,将null除外)
总结:
- ①
__proto__
和constructor
属性是对象所独有的;②prototype
属性是函数所独有的,因为函数也是一种对象,所以函数也拥有__proto__
和constructor
属性。__proto__
属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__
属性所指向的那个对象(父对象)里找,一直找,直到__proto__
属性的终点null,再往上找就相当于在null上取值,会报错。通过__proto__
属性将对象连接起来的这条链路即我们所谓的原型链。prototype
属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法,即f1.__proto__ === Foo.prototype
。constructor
属性的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function。
03_单文件组件
使用首字母大写
安装插件 Vetur 0.33.1 快捷键 <v
创建单文件组件
vue单文件:三个标签 template
结构 script
脚本 style
样式
App.vue
汇总所有组件
main.js
创建Vue实例,并指明容器
index.html
页面
注意没有使用脚手架无法直接使用浏览器运行出现想要的效果
<template>
<div class="demo">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>
<script>
export default {
name:'School', //与文件名保持一致
data(){
return {
name:'尚硅谷',
address:'北京昌平'
}
},
methods: {
showName(){
alert(this.name)
}
},
}
</script>
<style>
.demo{
background-color: orange;
}
</style>
ES6 模块化
三种暴露方式
export
:
- 分别暴露:
export const xxx = Vue.extend({...})
——import {xxx} from xxx
【命名导出name export】- 统一暴露:
export {xxx}
——import {xxx} from xxx
【命名导出name export】- 默认暴露:
export default xxx
【常用】——import xxx from xxx
【默认导出default export】注意可以不使用中转变量,直接默认暴露
VueComponent
三、使用Vue脚手架(CLI)
CLI-command line interface命令行接口工具
工具介绍:
babel
:ES6 ===> ES5
eslint
:语法检查
01_安装步骤
-
配置npm淘宝镜像:
npm config set registry https://registry.npm.taobao.org
-
全局安装脚手架:
npm install -g @vue/cli
(仅第一次执行) -
切换到要创建项目的目录,创建项目:
vue create xxxx
项目名(注意选择vue的版本正确)vue create
是vue-cli3.x的初始化方式,目前模板是固定的,模板选项可自由配置,创建出来的是vue-cli3的项目,与cue-cli2项目结构不同,配置方法不同,具体配置方法参考官方文档。
使用方式:vue create 项目名称
vue init
vue init 是vue-cli2.x的初始化方式,可以使用github上面的一些模板来初始化项目,webpack是官方推荐的标准模板名。vue-cli2.x项目向3.x迁移只需要把static目录复制到public目录下,老项目的src目录覆盖3.x的src目录(如果修改了配置,可以查看文档,用cli3的方法进行配置)
webpack是官方推荐的标准模板名
使用方式:vue init webpack 项目名称
electron-vue的模板
使用方式:vue init simulatedgreg/electron-vue 项目名称
-
启动项目:
npm run serve
-
停止项目:ctrl + C
02_分析脚手架文件结构
1.文件目录
├── node_modules:项目依赖的第三方包
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue【注意驼峰命名法】
│ │── App.vue: 汇总所有组件
│ │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件(缓存性文件)
2.package.json
3.html文件
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<!-- 针对IE浏览器的一个特殊配置,含义是让IE浏览器以最高的渲染级别渲染页面 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 开启移动端的理想视口 -->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 配置页签图标 -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- 引入第三方样式 -->
<link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">
<!-- 配置网页标题 -->
<title>硅谷系统</title>
</head>
<body>
<!-- 当浏览器不支持js时noscript中的元素就会被渲染 -->
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<!-- 容器 -->
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
<%= BASE_URL %>
指的是public文件夹所在的路径,使用./
、../
等容易出现路径错误
<%= htmlWebpackPlugin.options.title %>
配置网页标题,在package.json中找到name属性。(是webpack中的插件完成的功能
<noscript>
当浏览器不支持js时,标签内的内容将被渲染
4.main.js
该文件是整个项目的入口文件,不能随便改变名称
//引入Vue
import Vue from 'vue'
//引入App组件,它是所有组件的父组件
import App from './App.vue'
//关闭vue的生产提示
Vue.config.productionTip = false
//创建Vue实例对象---vm
new Vue({
el:'#app',
//render函数完成了这个功能:将App组件放入容器中
render: h => h(App),
// render:q=> q('h1','你好啊')
// template:`<h1>你好啊</h1>`,
// components:{App},
})
默认引入的vue
不是完整版的,而是dist/vue.runtime.esm.js
,不包含模板解析器,不能使用<template>
模板,完整版的是vue.js
文件
esm👉ES module
common👉commonJS
不同版本的Vue
- vue.js与vue.runtime.xxx.js的区别:
- vue.js是完整版的Vue,包含:核心功能 + 模板解析器。
- vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
Vue完整版中,模板解析器就占了将近3/1的体积,使用Webpack打包时已经将vue文件转换为js文件,使得模板解析器没有作用但却占了很大的体积。
package.json文件中的
"vue-template-compiler": "^2.6.14"
是专门用来解析vue文件中的template
的
- 因为vue.runtime.xxx.js没有模板解析器,所以不能使用
template
这个配置项,需要使用render
函数接收到的createElement
函数去指定具体内容。
render渲染函数中的第一个参数createElement
是一个函数,作为render返回值可以创建元素
createElement函数的第一个参数是标签名,第二个参数是内容。
如果传递的参数是Vue组件,则只需要一个参数:组件。
没有用到
this
所以简化为箭头函数。
5.修改脚手架默认配置
Vue脚手架隐藏了所有Webpack相关的配置
不可修改的文件结构
vue.config.js配置文件
- 使用
vue inspect > output.js
可以查看到Vue脚手架的默认配置。 - 使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh/config/
- 修改后需要重新启动项目
module.exports
使用的是CommonJS的暴露
03_ref属性
自闭合标签在非脚手架中会导致无法渲染后续组件,但在脚手架中就不存在这个问题了。
-
被用来给元素或子组件注册引用信息(id的替代者),用于给节点打标识。
-
应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
区别于DOM获取组件标签获取的是完整的DOM结构
-
使用方式:
- 标签上打标识:
<h1 ref="xxx">.....</h1>
或<School ref="xxx"></School>
- 利用组件实例对象vc
this
的$refs
属性读取:this.$refs.xxx
- 标签上打标识:
04_props配置
-
功能:让组件接收外部传过来的数据,用于父组件给子组件传递数据。【优先接收】
-
传递数据:
<Demo name="xxx"/>
需要从页面接收表达式值,需要使用v-bind
:
绑定数据 -
接收数据:
-
第一种方式(只接收):
props:['name']
-
第二种方式(限制类型):
props:{name:String}
-
第三种方式(限制类型type、限制必要性required、指定默认值default):
props:{ name:{ type:String, //类型 required:true, //必要性 default:'老王' //默认值 } }
备注:props是只读的(不属于data),Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
-
05_混入mixin
-
功能:可以把多个组件共用的配置提取成一个混入对象【复用配置】
-
使用方式:
第一步定义混合:
{ data(){....}, methods:{....} .... }
第二步使用混入:
全局混入:在main.js文件中引入minin文件中的配置并分别注册
Vue.mixin(xxx)
局部混入:在组件配置项中新增数组样式mixins:['xxx']
原则是不破坏原有的代码,注意
mount()
有所不同全局混入会使得vm、所有的vc上都有混合配置
06_插件plugins
-
功能:用于增强Vue,通过
install
方法给Vue或Vue实例添加方法,定义全局指令等 -
本质:包含
install
方法的一个对象-
第一个参数是Vue
-
第二个以后的参数是插件使用者传递的数据。
-
-
定义插件:
对象.install = function (Vue, options) { // 1. 添加全局过滤器 Vue.filter(....) // 2. 添加全局指令 Vue.directive(....) // 3. 配置全局混入(合) Vue.mixin(....) // 4. 添加实例方法 Vue.prototype.$myMethod = function () {...} Vue.prototype.$myProperty = xxxx }
-
使用插件:
Vue.use()
07_scoped样式
- 作用:让样式在局部生效,防止冲突。
- 写法:
<style scoped>
不适用于App.vue组件
08_TodoList案例
1. 组件化编码流程
1、拆分静态组件
组件要按照功能点拆分,命名不要与html元素冲突。
2、实现动态组件
考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
- 一个组件在用:放在组件自身即可。
- 一些组件在用:放在他们共同的父组件上(状态提升)。
单向绑定初始化数据 + 响应事件更新数据 = l双向绑定数据
注意当使用计算属性computed时,若需要双向绑定数据修改值时,需要为其分别配置getter和setter函数实现
3、实现交互
从绑定监听事件开始。
nanoid库
直接调用
nanoid()
函数生成独特id使用分别暴露import {nanoid} from ‘nanoid’引入nanoid
confirm()函数
确认框,区别于alert提示框只能确认,confirm框有确认和取消两个选择,分别对应函数返回值true和false。
2. props适用于
- 父传子:利用props属性【只读】
- 子传父:由父组件先给子组件传递一个函数,子组件通过调用父组件传来的props中的函数【JS引用】
不能直接在子组件中修改父组件传来的值
v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
因此不建议在实现勾选功能时,使用v-modle双向绑定实现,修改了props的todo.done的值。【eslint语法规范】
09_webStorage
-
存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
-
浏览器端通过
Window.sessionStorage
和Window.localStorage
属性来实现本地存储机制。 -
相关API:
-
xxxxxStorage.setItem('key', 'value');
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。 -
xxxxxStorage.getItem('person');
该方法接受一个键名作为参数,返回键名对应的值。
-
xxxxxStorage.removeItem('key');
该方法接受一个键名作为参数,并把该键名从存储中删除。
-
xxxxxStorage.clear()
该方法会清空存储中的所有数据。
-
-
备注:
-
SessionStorage存储的内容会随着浏览器窗口关闭而消失。
-
LocalStorage存储的内容,需要手动清除才会消失。
【用户清除浏览器缓存的时候也有可能清除掉】
-
因为存储时,会自动调用
toString
方法将Value转换为字符串。-
所以当存储一个对象时,需要使用
xxxxStorage.setItem('key',JSON.stringify(obj))
将对象变量转为JSON格式。 -
同时读取的时候,为了读出的是对象,需要使用
JSON.parse(xxxxStorage.getItem('key'))
将JSON转换为JS对象。
-
-
xxxxxStorage.getItem(xxx)
如果xxx对应的value获取不到,那么getItem的返回值是null。 -
JSON.parse(null)
的结果依然是null。(所以不会报错)获取的数据为null时,使用
||
或运算符,或一个初始值
使用侦听属性
watch
存储TodoList里的任务项时,需要使用深度侦听deep
,才能侦听到对象里面的数据变化。 -
10_组件的自定义事件
-
一种组件间通信的方式,适用于:子组件 ===> 父组件
-
使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A【父组件】中)。
-
绑定自定义事件:
-
[原始方法](###2. props适用于)【非自定义事件】:通过父元素创建回调函数,利用props传递给子元素调用。
-
第一种方式:使用
v-on
或@
(简写)在父组件中:
<Demo @atguigu="test"/>
或<Demo v-on:atguigu="test"/>
-
第二种方式:使用
ref
属性实现在父组件中:
<Demo ref="demo"/> //子组件的组件实例对象vc标识 ...... mounted(){ //在挂载App时触发自定义事件回调函数 // 利用setTimeout可以实现延时触发 // test为methods中写得回调函数 this.$refs.xxx.$on('atguigu',this.test) }
-
若想让自定义事件只能触发一次,可以使用
once
事件修饰符,或$once
方法。
-
-
触发自定义事件:
this.$emit('atguigu',数据)
【子组件中】 -
解绑自定义事件
this.$off('atguigu')
【子组件中】- 解绑多个自定义事件,参数需要写成字符串数组
[‘a’,’b’]
的形式 - 解绑所有的自定义事件,参数为空
- 解绑多个自定义事件,参数需要写成字符串数组
-
组件上也可以绑定原生DOM事件,需要使用
native
修饰符。没有使用修饰符的,Vue 始终认为绑定事件为自定义事件
Vue 3.0 中取消了
native
修饰符,直接使用原生事件也可以了 -
注意:通过
this.$refs.xxx.$on('atguigu',回调)
绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!【谁触发谁回调】在App中mounted触发自定义事件,回调函数中的
this
指向的是触发该事件的组件,而不是App -
自定义事件的调试
11_全局事件总线(GlobalEventBus)
-
一种组件间通信的方式,适用于任意组件间通信。【中介者模式,$bus作为中介者】
-
安装全局事件总线:在**
beforeCreate
**时绑定new Vue({ // main.js创建vm时 ...... beforeCreate() { Vue.prototype.$bus = this //安装全局事件总线,this就是当前应用的vm }, ...... })
-
使用事件总线:
-
接收数据:A组件想接收数据,则在A组件中给
$bus
绑定自定义事件,事件的回调留在A组件自身。// 组件中 methods(){ demo(data){......} // 回调函数写作methods } ...... mounted() { this.$bus.$on('xxxx',this.demo) // 绑定自定义事件 }
命名容易冲突==》会写一个config配置
$on
后的第二个参数回调函数- 可以提前在methods中写出,直接使用
this.xxx
调用 - 也可以直接将回调函数写在第二个参数处【箭头函数形式】
- 可以提前在methods中写出,直接使用
-
提供数据:
this.$bus.$emit('xxxx',数据)
-
-
最好在
beforeDestroy
钩子中,用$off
去解绑当前组件所用到的事件。注意
$off()
需要写参数,否则就是解绑所有总线上的事件注意哪里绑定就在哪里解绑
本质就是【自定义事件】只是将事件绑定给了$bus总线
12_消息订阅与发布(pubsub)
-
一种组件间通信的方式,适用于任意组件间通信。【观察者模式】
-
使用步骤:
-
安装pubsub:
npm i pubsub-js
【第三方库,教程是@1.6.0
版本的】 -
引入:
import pubsub from 'pubsub-js'
-
接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
methods(){ demo(MsgName,data){......} // 回调函数 } ...... mounted() { this.pubid = pubsub.subscribe('xxx',this.demo) //订阅消息 }
回调函数中第一个参数是传来的消息名称,第二个参数才是传递的数据。
若参数不使用,但需要占位使用
_
下划线占位。直接在订阅消息的回调函数处写匿名函数function(){ this //undefined },要么写成箭头函数形式,要么将回调函数写在methods里面才能保证this指向不出错。
-
提供数据:
pubsub.publish('xxx',数据)
-
最好在
beforeDestroy
钩子中,用PubSub.unsubscribe(pid)
去取消订阅。需要通过id去取消订阅,所以在订阅消息时需要一个参数接收id
-
第三方库==》Vue调试工具不能监听到
hasOwnProperty
在Vue中使用hasOwnProperty不能够直接使用,需要按照下述格式使用:
Object.prototype.hasOwnProperty.call(todo,'isEdit')
13_nextTick
- 语法:
this.$nextTick(回调函数)
- 作用:在下一次 DOM 更新结束后执行其指定的回调。
- 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
也可以使用不设时间的定时器
setTimeOut
来写使用场景:比如list是通过vue生成的,想要一生成就在DOM上面操作,比如聚焦……
14_Vue封装的过度与动画
-
作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。
-
图示:
-
写法:transition
-
准备好样式:
- 元素进入的样式:
- v-enter:进入的起点【transition过度】
- v-enter-active:进入过程中【animation动画】
- v-enter-to:进入的终点【transition过度】
- 元素离开的样式:
- v-leave:离开的起点【transition过度】
- v-leave-active:离开过程中【animation动画】
- v-leave-to:离开的终点【transition过度】
v 是默认的,如果给transition元素添加了name属性,v需要对应替换成name值
谁改变就给谁指定动画样式,也可以写给v-enter/leave-active
eg.
animation: xxx 0.5 liner;
或transition: 0.5s liner;
- 元素进入的样式:
-
使用
<transition>
包裹要过度的元素,并配置name属性:<transition name="hello"> <h1 v-show="isShow">你好啊!</h1> // 只能包裹1个元素 </transition>
-
备注:若有多个元素需要过度,则需要使用:
<transition-group>
,且每个元素都要指定key
值。 transisiton-group
-
-
使用第三方库【Animate.css】
-
安装 npm install animate.css
-
应用库
<transition-group name="animate_animated animate_bounce">
-
添加配置项
enter-active-class="animate_swing"
leave-active-class="animate_backOutUp"
-
四、Vue中的ajax
xhr new XMLHttpRequest() xhr.open() xhr.send()
jquery
$.get
$.post
主要是封装DOM操作的axios Promise风格(只有1层)、体积小【官方推荐】
fetch 与xhr平级,存在于window上,也是Promise风格(包装2层,需要两次.then才能拿到数据),兼容性不佳
vue-resource Vue插件库1.0中常用,对xhr的封装。用法和axios一样,需要先npm i vue-resource,在main.js中使用import vueResource from 'vue-resource’引入Vue.ues(vueResource)使用,然后将axios替换成
this.$http
即可。(为vc上多绑定了一个$http属性)
跨域问题CORS:协议、主机、端口号一致
如果后端是springmvc或者springboot项目,前端vue需要设置代理服务器,后端java也要配置解决cors跨域问题。
解决办法:
后端解决cors,响应时配置特殊响应头@CrossOrigin注解
jsonp解决:利用
script
标签里的src
属性,在引入外部资源的时候不受同源策略的限制。(较少使用,需要前后端配合,只能解决get请求)
配置代理服务器
- nginx
- vue-cli
同源策略只是限制前端浏览器,服务器之间都是使用http直接传输
01_脚手架配置代理
【配置完后需要重启服务器才能启动代理】
方法一
在vue.config.js中添加如下配置:
devServer:{
proxy:"http://localhost:5000"
}
说明:
- 优点:配置简单,请求资源时直接发给前端(8080)即可。
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
方法二
编写vue.config.js配置具体代理规则:
module.exports = {
devServer: {
proxy: {
'/api1': {// 匹配所有以 '/api1'开头的请求路径
target: 'http://localhost:5000',// 代理目标的基础路径
ws: true, //用于支持websockt
changeOrigin: true, //用于控制请求头中的host值
pathRewrite: {'^/api1': ''} //重写路径【必须写】{正则表达式:''} 利用正则替换请求前缀
},
'/api2': {// 匹配所有以 '/api2'开头的请求路径
target: 'http://localhost:5001',// 代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api2': ''}
}
}
}
}
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
changeOrigin默认值为true(React默认为false
*/
说明:
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
- 缺点:配置略微繁琐,请求资源时必须加前缀。
github案例
-
整体将html结构搬到App.vue的template中;
-
自定义样式先直接搬到App.vue的style中;
-
*第三方样式资源bootstrap放到assert中的css文件夹中,在App.vue文件script中进行引入(不在main.js中引入,因为是入口文件);
-
*注意使用import进行引入资源,会进行严格检查,只要用到了不存在的资源就会报错(之前在html里用link标签引入,不会严格检查就不会出现错误);
-
因此,推荐在public/css/下存放第三方样式,在index.html文件中通过link标签引入(注意引入的时候不要直接使用相对路径引入,需要和其他的一致使用<%= BASE_URL %>)。
-
利用ES6展开语法,可以在函数调用、数组构造时,将数组表达式或者string在语法层面展开;还可以在构造字面量对象时,将对象表达式按key-value的方式展开。
此处使用展开语法,比对给this.info进行赋值,即参照第一个参数
this.info
,与后面obj
一样的属性,以后面为准重新赋值,后面没有的属性,保留不动。且这样处理以后,obj中属性顺序不影响赋值。data() { return { info: { isFirst: true, isLoading: false, errMsg: "", users: [], }, }; }, mounted() { this.$bus.$on("updateListData", (obj) => { this.info = { ...this.info, ...obj }; }); },
02_slot插槽
-
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件 。
-
分类:默认插槽、具名插槽、作用域插槽
-
使用方式:
-
默认插槽
使用处组件需要写成成对的标签(而不是自闭合),将需要插入的内容写到标签中间。
子组件中使用slot标签定义插槽,内部可写默认内容,当未插入内容时显示。
-
具名插槽
原写法【已弃用】给slot标签指定name属性后为要插入的部分添加slot = “name”属性
新写法【vue 2.6】v-slot:name(不用将名字用引号“”框起来,只能用在template标签上)
-
作用域插槽
数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
组件中slot标签上绑定数据:data=“data”
App.vue中使用组件处必须使用template标签传插槽内容,使用属性slot-scope/scope=“自定义名称/{games}”获取数据,后续使用
自定义名称.data
即可使用数据,可以传递多个数据,作为对象传过来。直接使用{games}可以直接获取到数据games【ES6解构赋值】
-
五、Vuex
注意:vue2==》vuex3
vue3==》vuex4
01_原理
【概念】Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
【内容】这个状态自管理应用包含以下几个部分:
- state,驱动应用的数据源;
- view,以声明方式将 state 映射到视图;
- actions,响应在 view 上的用户输入导致的状态变化。
【什么时候用】当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:
- 多个视图依赖于同一状态。
- 来自不同视图的行为需要变更同一状态。
【工作原理图】
【工作原理图解析】
02_搭建环境
在工程的src目录下创建store文件夹,创建一个index.js文件编写store模块。
在引入的时候,因为index.js为默认的读取路径,所以直接‘./store’
即可在main.js中实现读取。
import语句会优先执行,即优先执行import文件中的代码,再执行其他代码。
-
创建文件:
src/store/index.js
//引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //应用Vuex插件 Vue.use(Vuex) //准备actions对象——响应组件中用户的动作 const actions = {} //准备mutations对象——修改state中的数据 const mutations = {} //准备state对象——保存具体的数据 const state = {} //创建并暴露store export default new Vuex.Store({ actions, mutations, state })
-
在
main.js
中创建vm时传入store
配置项...... //引入store import store from './store' ...... //创建vm new Vue({ el:'#app', render: h => h(App), store })
03_基本使用
-
初始化数据、配置
actions
、配置mutations
,操作文件store.js
//引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //引用Vuex Vue.use(Vuex) const actions = { //响应组件中加的动作 jia(context,value){ // console.log('actions中的jia被调用了',miniStore,value) context.commit('JIA',value) }, } const mutations = { //执行加 JIA(state,value){ // console.log('mutations中的JIA被调用了',state,value) state.sum += value } } //初始化数据 const state = { sum:0 } //创建并暴露store export default new Vuex.Store({ actions, mutations, state, })
-
组件中读取vuex中的数据:
$store.state.sum
-
组件中修改vuex中的数据:
$store.dispatch('action中的方法名',数据)
或$store.commit('mutations中的方法名',数据)
备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写
dispatch
,直接编写commit
注意:模板template里面能够看到vc上的所有东西,所以
{{}}
插值语法里读取数据不用加this
。为了方便区分actions和mutations,mutation中的方法名称全大写。
04_getters
-
概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。
-
在
store.js
中追加getters
配置...... const getters = { bigSum(state){ return state.sum * 10 } } //创建并暴露store export default new Vuex.Store({ ...... getters })
-
组件中读取数据:
$store.getters.bigSum
state-getters的关系类似于data-computed
05_四个map方法的使用
不可以直接在cmputed里写
{}
对象,使用ES6展开运算符…
,可以将一个对象里的内容全部展开
1. mapState
用于帮助我们映射state
中的数据为计算属性
computed: {
//借助mapState生成计算属性:sum、school、subject(对象写法)
...mapState({sum:'sum',school:'school',subject:'subject'}),
//借助mapState生成计算属性:sum、school、subject(数组写法)
...mapState(['sum','school','subject']),
},
2. mapGetters
用于帮助我们映射getters
中的数据为计算属性
computed: {
//借助mapGetters生成计算属性:bigSum(对象写法)
...mapGetters({bigSum:'bigSum'}),
//借助mapGetters生成计算属性:bigSum(数组写法)
...mapGetters(['bigSum'])
},
3. mapActions
用于帮助我们生成与actions
对话的方法,即:包含$store.dispatch(xxx)
的函数
methods:{
//靠mapActions生成:incrementOdd、incrementWait(对象形式)
...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
//靠mapActions生成:incrementOdd、incrementWait(数组形式)
...mapActions(['jiaOdd','jiaWait'])
}
4. mapMutations
用于帮助我们生成与mutations
对话的方法,即:包含$store.commit(xxx)
的函数
methods:{
//靠mapActions生成:increment、decrement(对象形式)
...mapMutations({increment:'JIA',decrement:'JIAN'}),
//靠mapMutations生成:JIA、JIAN(对象形式)
...mapMutations(['JIA','JIAN']),
}
备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。(绑定事件不写
()
,也会传参,参数是触发事件)注意:mapState、mapGetters是放在computed里,而mapActions、mapMutations是放在methods里。
06_模块化+命名空间
-
目的:让代码更好维护,让多种数据分类更加明确。
-
修改
store.js
const countAbout = { namespaced:true,//开启命名空间 state:{x:1}, mutations: { ... }, actions: { ... }, getters: { bigSum(state){ return state.sum * 10 } } } const personAbout = { namespaced:true,//开启命名空间 state:{ ... }, mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { countAbout, personAbout } })
-
开启命名空间后,组件中读取state数据:
//方式一:自己直接读取 this.$store.state.personAbout.list //方式二:借助mapState读取: ...mapState('countAbout',['sum','school','subject']),
-
开启命名空间后,组件中读取getters数据:
//方式一:自己直接读取 this.$store.getters['personAbout/firstPersonName'] //方式二:借助mapGetters读取: ...mapGetters('countAbout',['bigSum'])
-
开启命名空间后,组件中调用dispatch
//方式一:自己直接dispatch this.$store.dispatch('personAbout/addPersonWang',person) //方式二:借助mapActions: ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
-
开启命名空间后,组件中调用commit
//方式一:自己直接commit this.$store.commit('personAbout/ADD_PERSON',person) //方式二:借助mapMutations: ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
不同模块的store还可以分别建立文件对应,通过模块化引入到index.js文件中
六、Vue Router路由
- 路由:
- 一个路由(route)就是一组映射关系(key - value);
- 多个路由需要路由器(router)进行管理;
- key是路径,value可能是function(后端)或component组件(前端)。
-
vue-router:
- 是vue的一个插件库,专门用来实现SPA
- SPA(single page web application)单页面Web应用;
- 整个应用只有一个完整的页面;
- 点击页面中的导航链接不会刷新页面,只会做页面的局部更新;
- 数据需要通过ajax请求获取。
-
路由分类:
-
后端路由:value是function,用于处理客户端提交的请求。
工作过程:服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据。
-
前端路由:value是component,用于展示页面内容。
工作过程:当浏览器的路径改变时,对应的组件就会显示。
-
01_基本使用
-
安装vue-router,命令:
npm i vue-router
注意:vue2==》vue-router@3
vue3==》vue-router@4
-
应用插件:
Vue.use(VueRouter)
-
编写router配置项:
//引入VueRouter import VueRouter from 'vue-router' //引入Luyou 组件 import About from '../components/About' import Home from '../components/Home' //创建router实例对象,去管理一组一组的路由规则 const router = new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home } ] }) //暴露router export default router
-
实现切换(active-class可配置高亮样式)【router-link标签最终转换为a标签】
<router-link active-class="active" to="/about">About</router-link>
-
指定展示位置
<router-view></router-view>
02_几个注意点
- 路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹。 - 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息。 - 整个应用只有一个router,可以通过组件的
$router
属性获取到。
03_多级路由(嵌套路由)
-
配置路由规则,使用children配置项:
routes:[ { path:'/about', component:About, }, { path:'/home', component:Home, children:[ //通过children配置子级路由 { path:'news', //此处一定不要写:/news component:News }, { path:'message',//此处一定不要写:/message component:Message } ] } ]
-
跳转(要写完整路径):
<router-link to="/home/news">News</router-link> // 需要写父级路径
04_query参数
-
传递参数
<!-- 跳转并携带query参数,to的字符串写法 --> <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">跳转</router-link> <!-- 跳转并携带query参数,to的对象写法 --> <router-link :to="{ path:'/home/message/detail', query:{ id:666, title:'你好' } }" >跳转</router-link>
-
接收参数:
$route.query.id $route.query.title
05_命名路由
-
作用:可以简化路由的跳转。【当路径过长的时候】
-
如何使用
- 给路由命名:【借助name属性】
{
path:'/demo',
component:Demo,
children:[
{
path:'test',
component:Test,
children:[
{
name:'hello' //给路由命名
path:'welcome',
component:Hello,
}
]
}
]
}
- 简化跳转:【必须使用对象写法】
<!--简化前,需要写完整的路径 -->
<router-link to="/demo/test/welcome">跳转</router-link>
<!--简化后,直接通过名字跳转 -->
<router-link :to="{name:'hello'}">跳转</router-link>
<!--简化写法配合传递参数 -->
<router-link
:to="{
name:'hello',
query:{
id:666,
title:'你好'
}
}"
>跳转</router-link>
06_params参数
-
配置路由,声明接收params参数
{ path:'/home', component:Home, children:[ { path:'news', component:News }, { component:Message, children:[ { name:'xiangqing', path:'detail/:id/:title', //使用占位符声明接收params参数 component:Detail } ] } ] }
-
传递参数
<!-- 跳转并携带params参数,to的字符串写法 -->
<router-link :to="/home/message/detail/666/你好">跳转</router-link>
<!-- 跳转并携带params参数,to的对象写法 -->
<router-link
:to="{
name:'xiangqing',
params:{
id:666,
title:'你好'
}
}"
>跳转</router-link>
特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
-
接收参数:
$route.params.id $route.params.title
07_props配置
作用:让路由组件更方便的收到参数
{
name:'xiangqing',
path:'detail/:id',
component:Detail,
//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
props:{a:900}
//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
props:true
//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
props($route){
return {
id:$route.params.id, //可以传params参数
title:$route.query.title, //可以传query参数
name: 'name', //可以传自定义参数
}
}
}
08_<router-link>
的replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为
push
和replace
,push
是追加历史记录,replace
是替换当前记录。路由跳转时候默认为push
- 如何开启
replace
模式:<router-link replace .......>News</router-link>
09_编程式路由导航
-
作用:不借助
<router-link>
实现路由跳转,让路由跳转更加灵活 -
具体编码:
//$router的两个API
this.$router.push({
name:'xiangqing',
params:{
id:xxx,
title:xxx
}
})
this.$router.replace({
name:'xiangqing',
params:{
id:xxx,
title:xxx
}
})
this.$router.forward() //前进
this.$router.back() //后退
this.$router.go() //可前进也可后退
10.缓存路由组件
-
作用:让不展示的路由组件保持挂载,不被销毁。
-
具体编码:【include里面是组件名】
<keep-alive include="News"> <!-- :include="['News','Message']" 缓存多个 -->
<router-view></router-view>
</keep-alive>
11_两个新的生命周期钩子
- 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
- 具体名字:
activated
路由组件被激活时触发。deactivated
路由组件失活时触发。
12_路由守卫
-
作用:对路由进行权限控制
-
分类:全局守卫、独享守卫、组件内守卫
-
全局守卫:(前置守卫、后置守卫)
//全局前置守卫:初始化时执行、每次路由切换前执行 router.beforeEach((to,from,next)=>{ console.log('beforeEach',to,from) if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制 if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则 next() //放行 }else{ alert('暂无权限查看') // next({name:'guanyu'}) } }else{ next() //放行 } }) //全局后置守卫:初始化时执行、每次路由切换后执行 router.afterEach((to,from)=>{ console.log('afterEach',to,from) if(to.meta.title){ document.title = to.meta.title //修改网页的title }else{ document.title = 'vue_test' } })
-
独享守卫:(只有前置守卫)
beforeEnter(to,from,next){ console.log('beforeEnter',to,from) if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制 if(localStorage.getItem('school') === 'atguigu'){ next() }else{ alert('暂无权限查看') // next({name:'guanyu'}) } }else{ next() } }
-
组件内守卫:
//进入守卫:通过路由规则,进入该组件时被调用 beforeRouteEnter (to, from, next) { }, //离开守卫:通过路由规则,离开该组件时被调用 beforeRouteLeave (to, from, next) { }
设置路由中的
meta
属性,作为自定义传参载体。【路由元信息】通过路由规则进入,表示需要通过路由导航进入,理解一下。
13_路由器的两种工作模式
-
对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
-
hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
-
hash模式:
- 地址中永远带着#号,不美观 。
- 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
- 兼容性较好。
-
history模式:
-
地址干净,美观 。
-
兼容性和hash模式相比略差。
-
应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
node中可以使用npm安装connect-history-api-fallback解决。
nginx、java也都有自己的包,可以分辨前后端路由解决404的问题。
-
通过在router里,与routes同级配置
mode: ‘history’
切换,默认为hash模式。
npm run build
打包,生成dist文件夹,只打包src、public文件夹里的。打包出来的文件需要部署到服务器上,才能查看。
六、Vue UI组件库
01_移动端常用
02_PC端常用
-
按需引入时,babel.config.js中es2015报错的,改为@babel/preset-env或者@babel/env
Vue3快速上手
1.Vue3简介
- 2020年9月18日,Vue.js发布3.0版本,代号:One Piece(海贼王)
- 耗时2年多、2600+次提交、30+个RFC、600+次PR、99位贡献者
- github上的tags地址:https://github.com/vuejs/vue-next/releases/tag/v3.0.0
2.Vue3带来了什么
1.性能的提升
-
打包大小减少41%
-
初次渲染快55%, 更新渲染快133%
-
内存减少54%
…
2.源码的升级
-
使用Proxy代替defineProperty实现响应式
-
重写虚拟DOM的实现和Tree-Shaking
…
3.拥抱TypeScript
- Vue3可以更好的支持TypeScript
4.新的特性
-
Composition API(组合API)
- setup配置
- ref与reactive
- watch与watchEffect
- provide与inject
- …
-
新的内置组件
- Fragment
- Teleport
- Suspense
-
其他改变
- 新的生命周期钩子
- data 选项应始终被声明为一个函数
- 移除keyCode支持作为 v-on 的修饰符
- …
一、创建Vue3.0工程
1.使用 vue-cli 创建
官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create
## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 创建
vue create vue_test
## 启动
cd vue_test
npm run serve
2.使用 vite 创建
官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite
vite官网:https://vitejs.cn
- 什么是vite?—— 新一代前端构建工具。
- 优势如下:
- 开发环境中,无需打包操作,可快速的冷启动。
- 轻量快速的热重载(HMR)。
- 真正的按需编译,不再等待整个应用编译完成。
- 传统构建 与 vite构建对比图
## 创建工程
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev
二、常用 Composition API
官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html
1.拉开序幕的setup
- 理解:Vue3.0中一个新的配置项,值为一个函数。
- setup是所有Composition API(组合API)“ 表演的舞台 ”。
- 组件中所用到的:数据、方法等等,均要配置在setup中。
- setup函数的两种返回值:
- 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
- 若返回一个渲染函数:则可以自定义渲染内容。(了解)
- 注意点:
- 尽量不要与Vue2.x配置混用
- Vue2.x配置(data、methos、computed…)中可以访问到setup中的属性、方法。
- 但在setup中不能访问到Vue2.x配置(data、methos、computed…)。
- 如果有重名, setup优先。
- setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)
- 尽量不要与Vue2.x配置混用
2.ref函数
- 作用: 定义一个响应式的数据
- 语法:
const xxx = ref(initValue)
- 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。
- JS中操作数据:
xxx.value
- 模板中读取数据: 不需要.value,直接:
<div>{{xxx}}</div>
- 备注:
- 接收的数据可以是:基本类型、也可以是对象类型。
- 基本类型的数据:响应式依然是靠
Object.defineProperty()
的get
与set
完成的。 - 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数——
reactive
函数。
3.reactive函数
- 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用
ref
函数) - 语法:
const 代理对象= reactive(源对象)
接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象) - reactive定义的响应式数据是“深层次的”。
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
4.Vue3.0中的响应式原理
vue2.x的响应式
-
实现原理:
-
对象类型:通过
Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)。 -
数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
Object.defineProperty(data, 'count', { get () {}, set () {} })
-
-
存在问题:
- 新增属性、删除属性, 界面不会更新。
- 直接通过下标修改数组, 界面不会自动更新。
Vue3.0的响应式
-
实现原理:
-
通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
-
通过Reflect(反射): 对源对象的属性进行操作。
-
MDN文档中描述的Proxy与Reflect:
-
Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
-
Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
new Proxy(data, { // 拦截读取属性值 get (target, prop) { return Reflect.get(target, prop) }, // 拦截设置属性值或添加新属性 set (target, prop, value) { return Reflect.set(target, prop, value) }, // 拦截删除属性 deleteProperty (target, prop) { return Reflect.deleteProperty(target, prop) } }) proxy.name = 'tom'
-
-
5.reactive对比ref
- 从定义数据角度对比:
- ref用来定义:基本类型数据。
- reactive用来定义:对象(或数组)类型数据。
- 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过
reactive
转为代理对象。
- 从原理角度对比:
- ref通过
Object.defineProperty()
的get
与set
来实现响应式(数据劫持)。 - reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
- ref通过
- 从使用角度对比:
- ref定义的数据:操作数据需要
.value
,读取数据时模板中直接读取不需要.value
。 - reactive定义的数据:操作数据与读取数据:均不需要
.value
。
- ref定义的数据:操作数据需要
6.setup的两个注意点
-
setup执行的时机
- 在beforeCreate之前执行一次,this是undefined。
-
setup的参数
- props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
- context:上下文对象
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
this.$attrs
。 - slots: 收到的插槽内容, 相当于
this.$slots
。 - emit: 分发自定义事件的函数, 相当于
this.$emit
。
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
7.计算属性与监视
1.computed函数
-
与Vue2.x中computed配置功能一致
-
写法
import {computed} from 'vue' setup(){ ... //计算属性——简写 let fullName = computed(()=>{ return person.firstName + '-' + person.lastName }) //计算属性——完整 let fullName = computed({ get(){ return person.firstName + '-' + person.lastName }, set(value){ const nameArr = value.split('-') person.firstName = nameArr[0] person.lastName = nameArr[1] } }) }
2.watch函数
-
与Vue2.x中watch配置功能一致
-
两个小“坑”:
- 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
- 监视reactive定义的响应式数据中某个属性时:deep配置有效。
//情况一:监视ref定义的响应式数据 watch(sum,(newValue,oldValue)=>{ console.log('sum变化了',newValue,oldValue) },{immediate:true}) //情况二:监视多个ref定义的响应式数据 watch([sum,msg],(newValue,oldValue)=>{ console.log('sum或msg变化了',newValue,oldValue) }) /* 情况三:监视reactive定义的响应式数据 若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!! 若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 */ watch(person,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) },{immediate:true,deep:false}) //此处的deep配置不再奏效 //情况四:监视reactive定义的响应式数据中的某个属性 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //情况五:监视reactive定义的响应式数据中的某些属性 watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //特殊情况 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
3.watchEffect函数
-
watch的套路是:既要指明监视的属性,也要指明监视的回调。
-
watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
-
watchEffect有点像computed:
- 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
- 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。 watchEffect(()=>{ const x1 = sum.value const x2 = person.age console.log('watchEffect配置的回调执行了') })
8.生命周期
vue2生命周期图示
Vue3生命周期图示
- Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
beforeDestroy
改名为beforeUnmount
destroyed
改名为unmounted
- Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
beforeCreate
===>setup()
created
=======>setup()
beforeMount
===>onBeforeMount
mounted
=======>onMounted
beforeUpdate
===>onBeforeUpdate
updated
=======>onUpdated
beforeUnmount
==>onBeforeUnmount
unmounted
=====>onUnmounted
9.自定义hook函数
-
什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。
-
类似于vue2.x中的mixin。
-
自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。
10.toRef
-
作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
-
语法:
const name = toRef(person,'name')
-
应用: 要将响应式对象中的某个属性单独提供给外部使用时。
-
扩展:
toRefs
与toRef
功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)
三、其它 Composition API
1.shallowReactive 与 shallowRef
-
shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
-
shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
-
什么时候使用?
- 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。
2.readonly 与 shallowReadonly
- readonly: 让一个响应式数据变为只读的(深只读)。
- shallowReadonly:让一个响应式数据变为只读的(浅只读)。
- 应用场景: 不希望数据被修改时。
3.toRaw 与 markRaw
- toRaw:
- 作用:将一个由
reactive
生成的响应式对象转为普通对象。 - 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
- 作用:将一个由
- markRaw:
- 作用:标记一个对象,使其永远不会再成为响应式对象。
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类库等。
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
4.customRef
-
作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。
-
实现防抖效果:
<template> <input type="text" v-model="keyword"> <h3>{{keyword}}</h3> </template> <script> import {ref,customRef} from 'vue' export default { name:'Demo', setup(){ // let keyword = ref('hello') //使用Vue准备好的内置ref //自定义一个myRef function myRef(value,delay){ let timer //通过customRef去实现自定义 return customRef((track,trigger)=>{ return{ get(){ track() //告诉Vue这个value值是需要被“追踪”的 return value }, set(newValue){ clearTimeout(timer) timer = setTimeout(()=>{ value = newValue trigger() //告诉Vue去更新界面 },delay) } } }) } let keyword = myRef('hello',500) //使用程序员自定义的ref return { keyword } } } </script>
5.provide 与 inject
-
作用:实现祖与后代组件间通信
-
套路:父组件有一个
provide
选项来提供数据,后代组件有一个inject
选项来开始使用这些数据 -
具体写法:
-
祖组件中:
setup(){ ...... let car = reactive({name:'奔驰',price:'40万'}) provide('car',car) ...... }
-
后代组件中:
setup(props,context){ ...... const car = inject('car') return {car} ...... }
-
6.响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive
创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly
创建的只读代理 - isProxy: 检查一个对象是否是由
reactive
或者readonly
方法创建的代理
四、Composition API 的优势
1.Options API 存在的问题
使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。
2.Composition API 的优势
我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。
五、新的组件
1.Fragment
- 在Vue2中: 组件必须有一个根标签
- 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
- 好处: 减少标签层级, 减小内存占用
2.Teleport
-
什么是Teleport?——
Teleport
是一种能够将我们的组件html结构移动到指定位置的技术。<teleport to="移动位置"> <div v-if="isShow" class="mask"> <div class="dialog"> <h3>我是一个弹窗</h3> <button @click="isShow = false">关闭弹窗</button> </div> </div> </teleport>
3.Suspense
-
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
-
使用步骤:
-
异步引入组件
import {defineAsyncComponent} from 'vue' const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
-
使用
Suspense
包裹组件,并配置好default
与fallback
<template> <div class="app"> <h3>我是App组件</h3> <Suspense> <template v-slot:default> <Child/> </template> <template v-slot:fallback> <h3>加载中.....</h3> </template> </Suspense> </div> </template>
-
六、其他
1.全局API的转移
-
Vue 2.x 有许多全局 API 和配置。
-
例如:注册全局组件、注册全局指令等。
//注册全局组件 Vue.component('MyButton', { data: () => ({ count: 0 }), template: '<button @click="count++">Clicked {{ count }} times.</button>' }) //注册全局指令 Vue.directive('focus', { inserted: el => el.focus() }
-
-
Vue3.0中对这些API做出了调整:
-
将全局的API,即:
Vue.xxx
调整到应用实例(app
)上2.x 全局 API( Vue
)3.x 实例 API ( app
)Vue.config.xxxx app.config.xxxx Vue.config.productionTip 移除 Vue.component app.component Vue.directive app.directive Vue.mixin app.mixin Vue.use app.use Vue.prototype app.config.globalProperties
-
2.其他改变
-
data选项应始终被声明为一个函数。
-
过度类名的更改:
-
Vue2.x写法
.v-enter, .v-leave-to { opacity: 0; } .v-leave, .v-enter-to { opacity: 1; }
-
Vue3.x写法
.v-enter-from, .v-leave-to { opacity: 0; } .v-leave-from, .v-enter-to { opacity: 1; }
-
-
移除keyCode作为 v-on 的修饰符,同时也不再支持
config.keyCodes
-
移除
v-on.native
修饰符-
父组件中绑定事件
<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />
-
子组件中声明自定义事件
<script> export default { emits: ['close'] } </script>
-
-
移除过滤器(filter)
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。
-
…