VUE
1.初始化VUE
- 引入vue.js
直接下载,使用script标签导入或MDN引入,或npm
- 引入vue.js后,其向我们提供了一个构造函数Vue,在js中
new Vue()
后会返回 一个实例对象,定义一个变量接收const vm=new Vue()
- 在构造函数内传入一个对象参数{},
const vm=new Vue({})
2.VUE中的配置参数
①.el(或$mount)
作用:配置控制的元素,表示Vue要控制的区域,值为css选择器
<!-- 被Vue控制的区域,我们称之为模板 -->
<div id="app"></div>
const vm = new Vue({
el: '#app' // 控制id为app的元素
})
或
const vm = new Vue({})
vm.$mount('#app');
②.data
- 类型:对象
- 作用:存放要用到的数据,数据为响应式的
const vm = new Vue({ el: '#app', data: { ‘xxx’:'yyyy', } })
③.插值表达式
- 使用方法: {{ }}
- 可以将vue中的数据填在插值表达式中
数据包括:data中的变量、数字、字符串、布尔值、underfined、null、数组、对象、表达式(运算表达式、逻辑表达式、三元表达式、函数调用)
注意:在插值表达式中直接书写对象类型值时,不要将三个{}连在一起,这样会报错
注意:只要插值表达式中使用了数据,必须在data中声明过,否则会报错;
除非使用了data中引用类型(对象)中的未被声明的变量。
(在作用域上找不到,报错 ;在原型链上找不到,值为undefined;undefined为js基本类型值,所以就不报错啦 )
3.VUE的响应式原理
- 问:为什么data会直接出现在vm实例对象中咧?
答:当创建vue实例时,vue会将data中的成员代理给vue实例,目的是为了实现响应式,监控数据变化,执行某个监听函数
- 问:实例中除了data数据外,其他东西是啥子?
为了防止名称冲突。因为会将data中数据代理给vue,假如说我们自己写的data名称和vue中自带的属性冲突了,那么就会覆盖vue内部的属性,所以vue会把自己内部的属性成员名称前加上$或_,如果加上的是$,代表是我们可以使用的,如果加上的是_,是vue自己内部使用的方法或属性,我们不需要调用
(1)数据发生以下变化,均不会重新渲染页面
– 1、更改的数据必须是存在的数据,否则不能重新渲染页面,因为他监听不到;
–2、更改的数据必须已渲染过的数据,否则从性能角度考虑,不会重新渲染页面;
–3、利用索引直接设置一个数组项时;
–4、修改数组的长度时;
–5、添加或删除对象
(2)如何响应式的更新数组和对象?
更改数组:
1. 利用数组变异方法:push、pop、shift、unshift、splice、sort、reverse
2. 利用vm.$set/Vue.set实例方法
3. 利用vm.$set或Vue.set删除数组中的某一项
vm.$set是Vue.set的别名
使用方法:Vue.set(object, propertyName, value),也就是这个意思:Vue.set(要改谁,改它的什么,改成啥)
vm.$delete是Vue.delete的别名
使用方法:Vue.delete(object, target),也就是这个意思:Vue.delete(要删除谁的值,删除哪个)
- 更改数据后,页面会立刻重新渲染吗?
vue更新DOM的操作是异步执行的,只要侦听到数据变化,将开启一个异步队列,如果一个数据被多次变更,那么只会被推入到队列中一次,这样可以避免不必要的计算和DOM操作。
(3)vm.$nextTick & Vue.nextTick
- 如何在更改数据后,看到渲染后的页面上的值?
答:利用vm.$nextTick或Vue.nextTick,在页面重新渲染,DOM更新后,会立刻执行vm.$nextTick
使用方式:
①、
const vm = new Vue({})
// 1. 使用vm.$nextTick
vm.$nextTick(() => {})
// 2. 使用Vue.nextTick
Vue.nextTick(() => {})
②、vm.nextTick和Vue.nextTick还可以作为Promise使用
const vm = new Vue({})
// 1. 使用vm.$nextTick
vm.$nextTick().then(() => {})
// 2. 使用Vue.nextTick
Vue.nextTick().then(() => {})
- vm.$nextTick 和 Vue.nextTick的区别?
Vue.nextTick内部函数的this指向window
Vue.nextTick(function () {
console.log(this); // window
})
vm.$nextTick内部函数的this指向Vue实例对象
vm.$nextTick(function () {
console.log(this); // vm实例
})
- nextTick实现原理
– 异步任务分为宏任务(macro)和微任务(micro)
– 宏任务比较慢(如setTimeout等),微任务比较快(如Promise.then()等)
– 微任务在前,宏任务在后(eventloop,事件环)
– 在nextTick的实现源码中,会先判断是否支持微任务,不支持后,才会执行宏任务// 控制台打印顺序:promise > timeout setTimeout(() => { console.log('timeout'); }, 0) Promise.resolve().then(() => { console.log('promise'); })
if(typeof Promise !== 'undefined') { // 微任务 // 首先看一下浏览器中有没有promise // 因为IE浏览器中不能执行Promise const p = Promise.resolve(); } else if(typeof MutationObserver !== 'undefined') { // 微任务 // 突变观察 // 监听文档中文字的变化,如果文字有变化,就会执行回调 // vue的具体做法是:创建一个假节点,然后让这个假节点稍微改动一下,就会执行对应的函数 } else if(typeof setImmediate !== 'undefined') { // 宏任务 // 只在IE下有 } else { // 宏任务 // 如果上面都不能执行,那么则会调用setTimeout }
- 曾经vue用过的宏任务
- MessageChannel 消息通道 宏任务
(4)剖析Vue响应式原理
const data={
name:'lin',
age:18,
sex:'man',
love:{
food:'fish',
play:'swing'
},
hobby:['asfda','afasd'],
}
//针对数组变异方法的处理
const arrayProto=Array.prototype;
const arrayMethods=Object.create(arrayProto);//创建一个新的数组原型方法的对象
['push','pop','shift','unshift','sort','splice','reverse'].forEach(method=>{
arrayMethods[method]=function(){
arrayProto[method].call(this,...arguments);
render();//变异,原来数组的方法上没有重新渲染
}
})
function defineReactive(data,key,value){
observer(data[key]);
Object.defineProperty(data,key,{
get(){
console.log('du');
return value;
},
set(newValue){
if(newValue!=value){
value=newValue;
console.log('xie');
render();
}
}
})
}
function observer(data){
if(Array.isArray(data)){//为了防止数组情况出现,vue不能对数组进行递归遍历,因为太耗性能,所以使用数组的变异方法
data.__proto=arrayMethods;
return;
}
if(typeof data ==='object'){
for (let key in data) {
defineReactive(data,key,data[key]);
}
}
}
function render(){
console.log('页面渲染');
}
//实现$set
function $set(data,key,value){
if(Array.isArray(data)){
data.splice(key,1,value);
return value;
}
else if(typeof data ==='object'){
defineReactive(data,key,value);//给新添加的值也加上监听
render();
return value;
}
}
//实现$delete
function $delete(data,key){
if(Array.isArray(data)){
data.splice(key,1);
return;
}
delete data[key];
render();
}
observer(data);
4.VUE的相关指令
(1) v-pre
- 跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。
<!-- 不会被编译 --> <span v-pre>{{ msg }}</span>
(2)v-cloak
-
这个指令保持在元素上直到关联实例结束编译
-
可以解决闪烁的问题
-
和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕
[v-cloak] { display: none; }
<!-- {{ message }}不会显示,直到编译结束 --> <div v-cloak> {{ message }} </div>
(3)v-once
- 只渲染元素一次。随后的重新渲染,元素及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能
<!-- 单个元素 --> <span v-once>{{msg}}</span> <!-- 有子元素 --> <div v-once> <h1>comment</h1> <p>{{msg}}</p> </div>
(4)v-text
- 更新元素的 textContent
<span v-text="msg"></span> <!-- 和下面的一样 --> <span>{{msg}}</span>
v-text VS Mustache
- v-text替换元素中所有的文本,Mustache只替换自己,不清空元素内容
- v-text 优先级高于 {{ }}
(5)v-html
- 更新元素的innerHTML
- 注意:内容按普通 HTML 插入,不会作为 Vue 模板进行编译
- 在网站上动态渲染任意 HTML 是非常危险的,因为容易导致 XSS 攻击。只在可信内容上使用 v-html,永不用在用户提交的内容上。
(6)条件渲染指令v-if、v-else、v-else-if、v-show
①v-if
- 用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。
切换多个元素
- 因为 v-if 是一个指令,所以必须将它添加到一个元素上,但是如果想切换多个元素呢?此时可以把一个
<template>
元素当做不可见的包裹元素,并在上面使用 v-if。最终的渲染结果将不包含<template>
元素
② v-else
- 为 v-if 或者 v-else-if 添加“else 块”。
- 注意:前一兄弟元素必须有 v-if 或 v-else-if
③ v-else-if
- 表示 v-if 的 “else if 块”。可以链式调用。
- 注意:前一兄弟元素必须有 v-if 或 v-else-if
④ v-show
- 根据表达式之真假值,切换元素的 display CSS 属性。
<h1 v-show="ok">Hello!</h1>
注意 v-if VS v-show
- v-if 是惰性的,如果在初始渲染时条件为假,则什么也不做,直到条件第一次变为真时,才会开始渲染条件块。v-show则不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
- v-if 有更高的切换开销,v-show 有更高的初始渲染开销,如果需要非常频繁地切换,则使用 v-show 较好,如果在运行时条件很少改变,则使用 v-if 较好
- v-show不支持
<template>
元素 - v-show不支持v-else/v-else-if
(7)v-bind
-
作用:动态地绑定一个或多个特性
-
缩写 ‘:’
-
:后的为传递的参数(①固定参数,②动态参数)
<!-- 绑定一个属性 --> <img v-bind:src="imageSrc"> <!-- 动态特性名 (2.6.0+) --> <button v-bind:[key]="value"></button> <!-- 缩写 --> <img :src="imageSrc"> <!-- 动态特性名缩写 (2.6.0+) --> <button :[key]="value"></button> <!-- 内联字符串拼接 --> <img :src="'/path/to/images/' + fileName">
-
没有参数时,可以绑定到一个包含键值对的对象。注意此时 class 和 style 绑定不支持数组和对象。
<!-- 绑定一个有属性的对象 --> <div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>
-
由于字符串拼接麻烦且易错,所以在绑定 class 或 style 特性时,Vue做了增强,表达式的类型除了字符串之外,还可以是数组或对象。
-
绑定class
- 对象语法
上面的语法表示 active 这个 class 存在与否将取决于数据属性 isActive 的 真假。<div v-bind:class="{ red: isRed }"></div>
注意
:当属性名中存在-
等符号时要使用‘‘
将属性名括起来;若是不存在特殊符号则不需要。 - 数组语法
我们可以把一个数组传给 v-bind:class,以应用一个 class 列表<div v-bind:class="[classA, classB]"></div>
- 在数组语法总可以使用三元表达式来切换class
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
- 在数组语法中可以使用对象语法
<div v-bind:class="[classA, { classB: isB, classC: isC }]"> <div v-bind:class="classA" class="red">
- v-bind:class 可以与普通 class 共存
<div v-bind:class="classA" class="red">
- 对象语法
-
绑定style
- 使用对象语法
看着比较像CSS,但其实是一个JavaScript对象
CSS属性名可以用驼峰式(camelCase)或者短横线分隔(kebab-case)来命名
但是使用短横线分隔时,要用引号括起来<div v-bind:style="{ fontSize: size + 'px' }"></div>
也可以直接绑定一个样式对象,这样模板会更清晰:data: { size: 30 }
<div v-bind:style="styleObject"></div>
data: { styleObject: { fontSize: '13px' } }
- 使用数组语法
数组语法可以将多个样式对象应用到同一个元素<div v-bind:style="[styleObjectA, styleObjectB]"></div>
- 自动添加前缀
绑定style时,使用需要添加浏览器引擎前缀的CSS属性时,如 transform,Vue.js会自动侦测并添加相应的前缀。 - 多重值
从 2.3.0 起你可以为 style 绑定中的属性提供一个包含多个值的数组,常用于提供多个带前缀的值:
这样写只会渲染数组中最后一个被浏览器支持的值。在本例中,如果浏览器支持不带浏览器前缀的 flexbox,那么就只会渲染 display: flex。<div v-bind:style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
- 使用对象语法
-
-
缩写:
:
-
修饰符:
修饰符 (modifier) 是以英文句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。-
.camel
由于绑定特性时,会将大写字母转换为小写字母,如:<!-- 最终渲染的结果为:<svg viewbox="0 0 100 100"></svg> --> <svg :viewBox="viewBox"></svg>
所以,Vue提供了v-bind修饰符 camel,该修饰符允许在使用 DOM 模板时将 v-bind 属性名称驼峰化,例如 SVG 的 viewBox 属性
<svg :view-box.camel="viewBox"></svg>
-
.prop
被用于绑定 DOM 属性 (property)<div v-bind:text-content.prop="text"></div>
-
.sync
与组件的双向数据绑定有关
-
(8)v-on
- 作用:v-on 指令可以监听 DOM 事件,并在触发时运行一些 JavaScript 代码
- 缩写 ‘@’
- 事件类型由参数指定
<div id="app"> <button v-on:click="counter += 1">点击加 1</button> <p>按钮被点击了 {{ counter }} 次</p> </div>
const vm = new Vue({ el: 'app', data: { counter: 0 } })
- 但是很多事件处理逻辑是非常复杂的,所以直接把 JavaScript 代码写在 v-on 指令中是不可行的。所以 v-on 还可以接收一个需要调用的方法名称。
<div id="app"> <!-- `addCounter` 是在下面定义的方法名 --> <button v-on:click="addCounter">点击加 1</button> <p>按钮被点击了 {{ counter }} 次</p> </div>
const vm = new Vue({ el: '#app', data: { counter: 0 }, // 在 methods 对象中定义方法 methods: { addCounter: function (e) { // this 在方法里指向当前 Vue 实例 this.counter += 1; // e 是原生 DOM 事件 cosnole.log(e.target); } } })
在内联语句中使用 事件对象($event)
可以利用特殊变量 $event(当使用方法传入多个参数时,得传入$event才能使用e.target):
<div id="app">
<button v-on:click="addCounter(5, $event)">点击加 5</button>
<p>按钮被点击了 {{ counter }} 次</p>
</div>
new Vue({
el: '#app',
methods: {
addCounter: function (num, e) {
this.counter += 5;
cosnole.log(e.target);
}
}
})
绑定动态事件
Vue版本需要2.6.0+
<div v-on:[event]="handleClick">点击,弹出1</div>
const vm = new Vue({
el: '#app',
data: {
event: 'click'
},
methods: {
handleClick () {
alert(1);
}
}
})
不带参数绑定一个对象
Vue版本需要2.4.0+。
- { 事件名:事件执行函数 }
- 使用此种方法不支持函数传参&修饰符
<div v-on="{ mousedown: doThis, mouseup: doThat }"></div>
修饰符
① 事件修饰符
.stop
- 调用 event.stop,阻止事件冒泡
<!-- 此时只弹出button --> <div id="app"> <div @click="alert('div')"> <button @click.stop="alert('button')">点击</button> </div> </div>
const vm = new Vue({ el: '#app', methods: { alert(str) { alert(str); } } })
.prevent
- 调用 event.preventDefault(),阻止默认事件
<!-- 点击提交按钮后,页面不会重载 --> <div id="app"> <form v-on:submit.prevent="onSubmit"> <input type="submit"> </form> <!-- 也可以只有修饰符 --> <form v-on:submit.prevent> <input type="submit"> </form> </div>
const vm = new Vue({ el: '#app', methods: { onSubmit() { console.log('submit'); } } })
.capture
- 事件捕获模式
<!-- 此时先弹出div再弹出button --> <div id="app"> <div @click.capture="alert('div')"> <button @click="alert('button')">点击</button> </div> </div>
const vm = new Vue({ el: '#app', methods: { alert(str) { alert(str) } } })
.self
-
只当事件是从侦听器绑定的元素本身触发时才触发回调
<!-- 点击button时,只弹出 button --> <div id="app"> <div id="app"> <div :style="{ backgroundColor: 'red' }" @click.self="alert('div')"> <button @click="alert('button')">点击</button> </div> </div> </div>
const vm = new Vue({ el: '#app', methods: { alert(str) { alert(str) } } })
.once
-
只触发一次回调
-
2.1.4新增
点击两次button按钮,只弹出一次button <div id="app"> <button @click.once="alert('button')">点击</button> </div>
const vm = new Vue({ el: '#app', methods: { alert(str) { alert(str) } } })
.passive
- 设置 addEventListener 中的 passive 选项
- 能够提升移动端的性能
- 2.3.0新增
why passive?
- 即使在触发触摸事件时,执行了一个空的函数,也会让页面卡顿。因为浏览器不知道监听器到底会不会阻止默认事件,所以浏览器要等到执行完整个函数后,才能决定是否要滚动页面。passive事件监听器,允许开发者告诉浏览器,监听器不会阻止默认行为,从而浏览器可以放心大胆的滚动页面,这样可以大幅度提升移动端页面的性能,因为据统计只有20%的触摸事件会阻止默认事件。
- .passive 会告诉浏览器你不想阻止事件的默认行为
注意
- 使用修饰符时,顺序很重要。相应的代码会以同样的顺序产生。因此,
v-on:click.prevent.self 会阻止所有的点击的默认事件
v-on:click.self.prevent 只会阻止对元素自身点击的默认事件 - 不要把 .passive 和 .prevent 一起使用,因为 .prevent 将会被忽略,同时浏览器可能会向你展示一个警告。
②按键修饰符
在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">
你可以直接将 KeyboardEvent.key 暴露的任意有效按键名转换为 kebab-case 来作为修饰符。
<input v-on:keyup.page-down="onPageDown">
在上述示例中,处理函数只会在 $event.key 等于 PageDown 时被调用。
按键码
使用 keyCode 特性也是允许的:
<!-- 按回车键会触发执行submit函数 -->
<input v-on:keyup.13="submit">
注意:keyCode 的事件用法已经被废弃了,并可能不会被最新的浏览器支持。
为了在必要的情况下支持旧浏览器,Vue 提供了绝大多数常用的按键码的别名:
- .enter(回车键)
- .tab
- .delete (捕获“删除”和“退格”键)
- .esc
- .space (空格键)
- .up (箭头上键)
- .down (箭头下键)
- .left(箭头左键)
- .right(箭头右键)
除了使用Vue提供的按键别名之外,还可以自定义按键别名:
// 全局配置
// 可以使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112
Vue.config.keyCodes = {
v: 86,
f1: 112,
// 小驼峰 不可用
mediaPlayPause: 179,
// 取而代之的是 短横线分隔 且用双引号括起来
"media-play-pause": 179,
up: [38, 87]
}
<input type="text" @keyup.media-play-pause="method">
系统修饰键
可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。
修饰键与常规按键不同,在和 keyup 事件一起用时,事件触发时修饰键必须处于按下状态,换句话说,只有在按住 ctrl 的情况下释放其它按键,才能触发 keyup.ctrl。而单单释放 ctrl 也不会触发事件。如果你想要这样的行为,请为 ctrl 换用 keyCode:keyup.17。
- .ctrl
- .alt
- .shift
- .meta
在 Mac 系统键盘上,meta 对应 command 键 (⌘)。
在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞)。
在 Sun 操作系统键盘上,meta 对应实心宝石键 (◆)。
在其他特定键盘上,尤其在 MIT 和 Lisp 机器的键盘、以及其后继产品,比如 Knight 键盘、space-cadet 键盘,meta 被标记为“META”。
在 Symbolics 键盘上,meta 被标记为“META”或者“Meta”
<!-- Alt + C -->
<input @keyup.alt.67="clear">
<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>
exact 修饰符
- 允许你控制由精确的系统修饰符组合触发的事件。
- 2.5.0 +
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>
③鼠标按钮修饰符
- 仅当点击特定的鼠标按钮时会处理执行函数
- 2.2.0 +
- .left
- .right
- .middle
(9)v-for 列表渲染
利用v-for指令,基于数据多次渲染元素。
①在v-for中使用数组
用法:(item, index) in items
参数:items: 源数据数组
item:数组元素别名
index:可选,索引
可以访问所有父作用域的属性
<ul id="app">
<li v-for="(person, index) in persons">
{{ index }}---{{ person.name }}---{{ person.age }}
</li>
</ul>
const vm = new Vue({
el: '#app',
data: {
persons: [
{ name: '杉杉', age: 18 },
{ name: '思彤哥', age: 20 },
{ name: '成哥', age: 22 },
{ name: '邓哥', age: 88 },
]
}
})
可以利用of
替代in
作为分隔符,因为它更接近迭代器的语法:
<div v-for="item of items"></div>
②在v-for中使用对象
用法:(value, key, index) in Object
参数:value: 对象值
key:可选,键名
index:可选,索引
<ul id="app">
<li v-for="(value, key, index) in shan">
{{ value }}
</li>
</ul>
const vm = new Vue({
el: '#app',
data: {
shan: {
name: '杉',
age: 18,
height: '163cm'
}
}
})
③在v-for中使用数字
用法:n in num
参数:n: 数字,从1开始
<div>
<span v-for="n in num">{{ n }} </span>
</div>
const vm = new Vue({
el: '#app',
data: {
num: 10
}
})
④在v-for中使用字符串
用法:str in string
参数:str: 字符串,源数据字符串中的每一个
<div>
<span v-for="str in string">{{ str }} </span>
</div>
const vm = new Vue({
el: '#app',
data: {
string: 'shanshan'
}
})
⑤循环一段包含多个元素的内容
可以利用template元素循环渲染一段包含多个元素的内容
<ul id="app">
<template v-for="person in persons">
<li>{{ item.msg }}</li>
<li>哈哈</li>
</template>
</ul>
const vm = new Vue({
el: '#app',
data: {
persons: ['shan', 'jc', 'cst', 'deng']
}
})
⑥关于key
Vue更新使用v-for渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是简单复用此处每个元素:
<ul id="app">
<li v-for="(person, index) in persons">
{{ person }}
<input type="text" />
<button @click="handleClick(index)">下移</button>
</li>
</ul>
const vm = new Vue({
el: '#app',
data: {
persons: ['shan', 'jc', 'cst', 'deng']
},
methods: {
handleClick (index) {
const deleteItem = this.persons.splice(index, 1);
this.persons.splice(index + 1, 0, ...deleteItem);
}
}
})
在"就地复用"策略中,点击按钮,输入框不随文本一起下移,是因为输入框没有与数据绑定,所以vuejs默认使用已经渲染的dom,然而文本是与数据绑定的,所以文本被重新渲染。这种处理方式在vue中是默认的列表渲染策略,因为高效。
这个默认的模式是高效的,但是在更多的时候,我们并不需要这样去处理,所以,为了给Vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,我们需要为每项提供一个唯一key特性,Vue会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。
key的使用方法
预期值:number | string
有相同父元素的子元素必须有独特的 key,重复的 key 会造成渲染错误,key应唯一。
<ul id="app">
<li v-for="(person, index) in persons" :key="person">
{{ person }}
</li>
</ul>
const vm = new Vue({
el: '#app',
data: {
persons: ['杉杉', '思彤哥', '成哥', '邓哥']
}
})
不建议将数组的索引作为key值,如:
<li v-for="(person, index) in persons" :key="index">
{{ person }}
</li>
当改变数组时,页面会重新渲染,Vue会根据key值来判断要不要移动元素。例如当页面重新渲染时,key值为"杉杉"的元素为<li>杉杉</li>
,页面重新渲染前,key值为"杉杉"的元素也为<li>杉杉</li>
,那么Vue就会移动这个li
元素,而不是重新生成一个元素。
当使用数组的索引作为key值时,页面重新渲染后,元素的key值会重新被赋值,例如我们将数组进行反转,
反转前:
元素 | key值 |
---|---|
<li>杉杉</li> | 0 |
<li>思彤哥</li> | 1 |
<li>成哥</li> | 2 |
<li>邓哥</li> | 3 |
反转后:
元素 | key值 |
---|---|
<li>邓哥</li> | 0 |
<li>成哥</li> | 1 |
<li>思彤哥</li> | 2 |
<li>杉杉</li> | 3 |
Vue会比对渲染前后拥有同样key的元素,发现有变动,就会再生成一个元素,如果用索引作key值得话,那么此时,所有的元素都会被重新生成。
那么key如何唯一的?
跟后台协作时,传回来的每一条数据都有一个id值,这个id就是唯一的,用id做key即可。
key不仅为v-for所有,它可以强制替换元素,而不是重复使用它:
<ul id="app">
<button @click="show = !show">{{ show ? '显示' : '隐藏'}}</button>
<input type="text" v-if="show" key="a" />
<input type="text" v-else key="b" />
</ul>
const vm = new Vue({
el: '#app',
data: {
show: true
}
})
⑦v-for 和 v-if 一同使用
永远不要把 v-if 和 v-for 同时用在同一个元素上。
当 Vue 处理指令时,v-for 比 v-if 具有更高的优先级,所以这个模板:
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
将会经过如下运算:
this.users.map(function (user) {
if (user.isActive) {
return user.name
}
})
(10)v-model
可以在表单元素上(input、textarea、select)创建双向数据绑定。即数据更新元素更新、元素更新数据也会更新。
本质上v-model为语法糖
元素类型 | 属性 | 事件 |
---|---|---|
input[checkbox]、input[radio] | checked | change |
select | value | change |
input[type=text]、textarea | value | input |
①input
ⅰ、type=text 文本框
<div id="app">
<input v-model="message">
<p>Message 为: {{ message }}</p>
</div>
const vm = new Vue({
el: '#app',
data:; {
message: ''
}
})
ⅱ、type=checkbox 复选框
单个复选框
绑定到布尔值,v-model=“Boolean”
<div id="app">
<input
type="checkbox"
id="checkbox"
v-model="checked"
/>
<label for="checkbox">{{ checked }}</label>
</div>
const vm = new Vue({
el: '#app',
data: {
checked: true
}
})
多个复选框
绑定到同一个数组,v-model=“Array”
数组中的值为被选中的input框value值
<div id="app">
<input type="checkbox" id="cheng" value="成哥" v-model="checkedNames">
<label for="cheng">成哥</label>
<input type="checkbox" id="deng" value="邓哥" v-model="checkedNames">
<label for="deng">邓哥</label>
<input type="checkbox" id="tong" value="思彤哥" v-model="checkedNames">
<label for="tong">思彤哥</label>
<br>
<span>被选中的人有: {{ checkedNames }}</span>
</div>
const vm = new Vue({
el: '#app',
data: {
checkedNames: []
}
})
ⅲ、type=radio 单选框
被绑定的数据和value同步
<div id="app">
<input type="radio" id="cheng" value="成哥" v-model="picked">
<label for="cheng">成哥</label>
<input type="radio" id="deng" value="邓哥" v-model="picked">
<label for="deng">邓哥</label>
<input type="radio" id="tong" value="思彤哥" v-model="picked">
<label for="deng">思彤哥</label>
<br>
<span>被选中的人: {{ picked }}</span>
</div>
const vm = new Vue({
el: '#app',
data: {
picked: ''
}
})
②textarea
<div id="app">
<p >多行文本为:{{ message }}</p>
<textarea v-model="message" placeholder="添加文本"></textarea>
</div>
const vm = new Vue({
el: '#app',
data: {
message: ''
}
})
③select
匹配的值为option中的汉字
ⅰ、单选
<div id="app">
<select v-model="selected">
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>选择: {{ selected === '请选择' ? '' : selected }}</span>
</div>
const vm = new Vue({
el: '#app',
data: {
selected: '请选择'
}
})
注意:如果 v-model 表达式的初始值未能匹配任何选项,<select>
元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,可以提供一个值为空的禁用选项:
<div id="app">
<select v-model="selected">
<option :disabled="selected">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>选择: {{ selected === '请选择' ? '' : selected }}</span>
</div>
ⅱ、多选
绑定到一个数组
<div id="app">
<select v-model="selected" multiple>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>选择: {{ selected }}</span>
</div>
const vm = new Vue({
el: '#app',
data: {
selected: []
}
})
④修饰符
.lazy
在默认情况下,v-model在每次input事件触发后将输入框的值与数据进行同步。如果要变为使用change事件同步可以添加lazy修饰符:
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" >
.number
自动将用户的输入值转为数值类型:
<input v-model.number="age" type="number">
.trim
自动过滤用户输入的首尾空白字符:
<input v-model.trim="msg">
5.VUE的计算属性 computed
有些时候,我们在模板中放入了过多的逻辑,从而导致模板过重,且难以维护。
基础用法
计算属性是Vue配置对象中的属性,使用方式如下:
<div id="app">
<!-- 计算属性的值可以像data数据一样,直接被使用 -->
{{ someComputed }}
</div>
const vm = new Vue({
el: '#app',
computed: {
// 返回的值,就是计算属性的值
someComputed () {
return 'some values'
}
}
})
(1) 计算属性 vs 方法
虽然在表达式中调用方法也可以实现同样的效果,但是使用计算属性
和使用方法
有着本质的区别。
当使用方法
时,每一次页面重新渲染,对应的方法都会重新执行一次,如:
<div id="app">
<p>{{ name }}</p>
<p>{{ reversedMsg() }}</p>
</div>
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello',
name: 'shanshan'
},
methods: {
reversedMsg: function () {
console.log('方法执行啦');
return this.msg.split('').reverse().join('');
}
}
})
vm.name = 'duyi';
在上面的例子中我们可以看到,一旦更改name的值,页面会重新渲染,此刻控制台中打印出方法执行啦
这串字符串,代表着reversedMsg这个函数执行了,但是我们并不需要该方法执行,因为改动的数据和这个函数没有任何关系,如果这个函数内的逻辑很复杂,那么对于性能来讲,也是一种消耗。
但是利用计算属性
做,就不会有这样的现象出现,如:
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello',
name: 'shanshan'
},
computed: {
reversedMsg: function () {
console.log('计算执行啦');
return this.msg.split('').reverse().join('');
}
}
})
vm.name = 'duyi';
此时可以看到,当给数据name重新赋值时,计算属性并没有执行。
所以,计算属性
和方法
的最本质的区别是:计算属性是基于响应式依赖进行缓存的,计算属性的值一直存于缓存中,只要它依赖的data数据不改变,每次访问计算属性,都会立刻返回缓存的结果,而不是再次执行函数。而方法则是每次触发重新渲染,调用方法将总会再次执行函数。
那么,为什么需要缓存呢?
假如说,我们有一个计算属性A,它需要遍历一个巨大的数组并且做巨大的计算。然后我们需要使用到这个计算属性A,如果没有缓存,我们就会再次执行A的函数,这样性能开销就变得很大了。
(2)计算属性的内部属性
计算属性除了写成函数形式,还可以写成对象形式。对象内有两个属性getter和setter
,这两个属性均为函数。
const vm = new Vue({
el: '#app',
computed: {
keyName: {
getter () {
// 一些代码
},
setter () {
// 一些代码
}
}
}
})
①getter读取
在前面,我们直接将计算属性写成了一个函数,这个函数即为getter函数。也就是说,计算属性默认只有getter。
getter的this,被自动绑定为Vue实例。
何时执行?
当我们去获取某一个计算属性时,就会执行get函数。
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello'
},
computed: {
reversedMsg: {
getter () {
return this.msg.split('').reverse().join('');
}
}
}
})
②setter设置
可选,set函数在给计算属性重新赋值时会执行。
参数:为被重新设置的值。
setter的this,被自动绑定为Vue实例。
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello',
firstStr: ''
},
computed: {
reversedMsg: {
getter () {
return this.msg.split('').reverse().join('');
},
setter (newVal) {
this.firstStr = newVal[0];
}
}
}
})
注意:
即使给计算属性赋了值,计算属性也不会改变。
只有当依赖的响应式属性变化了,计算属性才会重新计算。
6.VUE的侦听器 watch
侦听属性,响应数据(data&computed)的变化,当数据变化时,会立刻执行对应函数
(1)值类型
①函数类型
例:
const vm = new Vue({
el: '#app',
data: {
msg: 'hello,你好呀,我是杉杉',
},
watch: {
msg () {
console.log('msg的值改变啦~');
}
}
})
// 更改msg的值
vm.msg = 'hello~~~~'; // 此时会在控制台中打印出` msg的值改变啦 `
侦听器函数,会接收两个参数,第一个参数为newVal(被改变的数据),第二个参数为oldVal(赋值新值之前的值)。如在上述代码中,将侦听器watch更改一下,如:
watch: {
msg (newVal,oldVal) {
conosle.log(newVal, oldVal);
}
}
// 更改msg的值
vm.msg = 'hello~~~~'; // 此时会在控制台中打印出`hello,你好呀,我是杉杉 hello~~~~`
②字符串类型
值(即字符串)为方法的名字,被侦听的数据改变时,会执行该方法。
const vm = new Vue({
el: '#app'
data: {
msg: '杉杉'
},
watch: {
msg: 'msgChange'
},
methods: {
msgChange () {
console.log('msg的值改变啦');
}
}
})
vm.msg = 'hello'; // 此时msgChange函数会执行,控制台中打印出 ` msg的值改变啦 `
③对象类型
写成对象类型时,可以提供选项,对象中有三个属性handler、deep、immediate。
handler
必需。handler时被侦听的数据改变时执行的回调函数。
handler的值类型为函数/字符串,写成字符串时为一个方法的名字。
const vm = new Vue({
el: '#app'
data: {
msg: '杉杉'
},
watch: {
msg: {
handler () {
console.log('msg的值改变啦');
}
}
}
})
vm.msg = 'hello'; // 此时回调函数会执行,控制台中打印出 ` msg的值改变啦 `
deep
在默认情况下,侦听器侦听对象只侦听引用的变化,只有在给对象赋值时它才能被监听到。所以需要使用deep选项,让其可以发现对象内部值的变化,将deep的值设置为true,那么无论该对象被嵌套的有多深,都会被侦听到。
const vm = new Vue({
el: '#app'
data: {
personObj: {
name: '邓旭明',
age: 88
}
},
watch: {
personObj: {
handler () {
console.log('对象的值改变啦');
},
deep: true // 开启深度侦听
}
}
})
vm.obj.name = '老邓头'; // 此时回调函数会执行,控制台中打印出 ` 对象的值改变啦 `
注意,当对象的属性较多的时候,性能开销会比较大,此时可以监听对象的某个属性,这个后面再说。
immediate
加上immediate选项后,回调将会在侦听开始之后立刻被调用。而不是等待侦听的数据更改后才会调用。
const vm = new Vue({
el: '#app'
data: {
msg: '杉杉'
},
watch: {
msg: {
handler () {
console.log('回调函数执行啦');
},
immediate: true
}
}
})
// 此时未更改msg的值,就会在控制台打印出来` 回调函数执行啦 `
④数组类型
可以将多种不同值类型写在一个数组中。如:
const vm = new Vue({
el: '#app'
data: {
msg: '杉杉'
},
watch: {
msg: [
'msgChange',
function () {},
{
handler () {},
deep: true,
immediate: true
}
]
}
})
(2)键类型
正常对象key值
以上演示的都是正常的对象key值,这里不再赘述。
字符串类型key值
当key值类型为字符串时,可以实现监听对象当中的某一个属性,如:
const vm = new Vue({
el: '#app'
data: {
personObj: {
name: '邓旭明',
age: 88
}
},
watch: {
'personObj.name' () {
console.log('对象的值改变啦');
}
}
})
vm.obj.name = '老邓头'; // 此时回调函数会执行,控制台中打印出 ` 对象的值改变啦 `
(3)vm.$watch
Vue实例将会在实例化时调用$watch,遍历watch对象的每一个属性。
我们也可以利用vm.$watch来实现侦听,用法与watch选项部分一致,略有不同。以下为使用方法。
①侦听某个数据的变化
// 1. 三个参数,一参为被侦听的数据;二参为数据改变时执行的回调函数;三参可选,为设置的选项对象
vm.$watch(
'msg',
function () {
// 干了点事儿
},
{
deep: Boolean,
immediate: Boolean
}
)
// 2. 二个参数,一参为被侦听的数据;二参为选项对象,其中handler属性为必需,是数据改变时执行的回调函数,其他属性可选。
vm.$watch(
'msg',
{
handler () {
// 干了点事儿
},
deep: Boolean,
immediate: Boolean
}
)
②侦听某个对象属性的变化
vm.$watch('obj.name', /**参数和上面一样*/)
③当监听的数据的在初始不确定,由多个数据得到时,此时可以将第一个参数写成函数类型
vm.$watch(function () {
// 表达式`this.a + this.b`每次得出一个不同的结果时该函数都会被调用
// 这就像监听一个未被定义的计算属性
return this.a + this.b;
}, /**参数和上面一致*/)
④取消侦听
侦听器函数执行后,会返回一个取消侦听函数,用来停止触发回调:
const unwatch = vm.$watch('msg', function () {});
unwatch(); // 执行后会取消侦听msg数据
使用unwatch时,需要注意
的是,在带有immediate选项时,不能在第一次回调时取消侦听数据。
//错误做法
const unwatch = vm.$watch('msg', function () {
// 干了点儿事
unwatch(); // 此时会报错
},{
immediate: true
}
})
如果仍然希望在回调内部用一个取消侦听的函数,那么可以先检查该函数的可用性:
//真确做法
var unwatch = vm.$watch('msg', function () {
// 干了点儿事
if(unwatch) {
unwatch();
}
},{
immediate: true
}
})
(4)侦听器 vs 计算属性
-
两者都可以观察和响应Vue实例上的数据的变动。
-
watch擅长处理的场景是:一个数据影响多个数据(因为一次侦听只能对某个属性或数据进行,将属性或数据名作为侦听函数名或对象名);
computed擅长处理的场景是:多个数据影响一个数据(根据数据的变化,得到新的值); -
在侦听器中可以执行异步,但是在计算属性中不可以(因为:计算属性函数中必须使用return将处理的结果返回;而侦听属性不需要return。
详细解释:计算属性已经将结果返回,但此时异步处理还未进行,所以此时返回的结果不是最终结果;而watch没有return,可以将异步处理完成的结果返回。)
例:
使用侦听器:
var vm = new Vue({
el: '#app',
data: {
question: '',
},
watch: {
question () {
setTimeout(() => {
alert(this.question);
}, 1000)
}
}
})
7.Axios
Axios是一个基于promise的HTTP库
浏览器支持情况:Chrome、Firefox、Safari、Opera、Edge、IE8+
(1)引入
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
(2)API
- axios(config)
- axios(url, [config])
(3)config 配置对象
最常用的配置:
axios({
method: 'get', // post、get、put....
baseURL: '', // 请求的域名,基本地址
url: '', // 请求的路径
params: {}, // 会将请求参数拼接在url上
data: {}, // 会将请求参数放在请求体中
headers: {}, // 设置请求头,例如设置token等
timeout: 1000, // 设置请求超时时长,单位:ms
})
(4)方法别名
为方便起见,为所有支持的请求方法提供了别名。
- axios.request(config)
- axios.get(url, [config])
- axios.post(url, [data], [config]])
- axios.delete(url, [config])
- axios.head(url, [config])
- axios.put(url, [data], [config])
- axios.patch(url, [data], [config]])
- axios.options(url, [config])
(5)配置默认值
可以指定将被用在各个请求的配置默认值
全局配置
axios.defaults.baseURL = 'https://developer.duyiedu.com/vue';
axios.defaults.timeout = 1000;
在实际项目中,很少用全局配置。
实例配置
可以使用自定义配置新建一个axios实例
const instance = axios.create({
baseURL: 'https://developer.duyiedu.com/vue',
timeout: 1000,
})
instance.get('/getUserInfo').then(res => {
// ...
})
请求配置
const instance = axios.create();
instance.get('/getUserInfo', {
timeout: 5000
})
配置的优先顺序
全局 < 实例 < 请求
(6)并发
同时进行多个请求,并统一处理返回值
- axios.all(iterable)
- axios.spread(callback)
axios.all([
axios.get('/a'),
axios.get('/b')]).then(axios.spread((aRes,bRes)=>{
console.log(aRes,bRes);
}))
(7)拦截器
interceptors,在发起请求之前做一些处理,或者在响应回来之后做一些处理。
①请求拦截器
axios.interceptors.request.use(config => {
// 在发送请求之前做些什么
return config;
})
②响应拦截器
axios.interceptors.response.use(response =>{
//对相应数据做点什么
return response;
})
③移除拦截器
const myInterceptor=axios.interceptor.request.use(config => {
....
return config;
})
axios.interceptor.request.eject(myInterceptor);
④为axios实例添加拦截器
const instance = axios.create({
...
});
instance.interceptor.request.use(config => {
...
return config;
})
⑤取消请求
应用于多次跳转页面,取消前一次正在进行的http请求数据的请求,提高效率。
const source = axios.CancelToken.source();//首先
axios.get('/getUserInfo',{
cancelToken:source.token,
}).then(res => {
console.log(res);
}).catch(error => {
if(axios.isCancel(errror)){
//取消请求
}else{
//其他错误
}
})
//vue中的:(调用函数取消请求)
methods:{
handle(){
source.cancel('取消请求');
}
}
(8)错误处理
在请求错误时进行的处理
request / response 是error的上下文,标志着请求发送 / 得到响应
在错误中,如果响应有值,则说明是响应时出现了错误。
如果响应没值,则说明是请求时出现了错误。
在错误中,如果请求无值,则说明是请求未发送出去,如取消请求。
axios.get('/user/12345')
.catch(function (error) {
// 错误可能是请求错误,也可能是响应错误
if (error.response) {
// 响应错误
} else if (error.request) {
// 请求错误
} else {
console.log('Error', error.message);
}
console.log(error.config);
});
在实际开发过程中,一般在拦截器中统一添加错误处理
请求拦截器中的错误,会当请求未成功发出时执行,但是要注意的是:取消请求后,请求拦截器的错误函数也不会执行,因为取消请求不会抛出异常(只有在移除拦截器时出现异常),axios对其进行了单独的处理。
在更多的情况下,我们会在响应拦截器中处理错误。
const instance = axios.create({});
instance.interceptors.request.use(config => {
}, error => {
return Promise.reject(error);
})
instance.interceptors.response.use(response => {
}, error => {
return Promise.reject(error);
})
(9)axios 预检
当axios的请求为非简单请求时,浏览器会进行预检,及发送OPTIONS请求。请求到服务器,询问是否允许跨域。如果响应中允许预检中请求的跨域行为,则浏览器会进行真正的请求。否则会报405错误。
8.template 选项
(在vue的配置对象中,如data、el、methods、computed、watch等等,均叫为选项)
关于el
提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。可以是 CSS 选择器,也可以是一个 HTML 元素 实例。
如果在实例化时存在这个选项,实例将立即进入编译过程,否则,需要显式调用 vm.$mount() 手动开启编译。
template
一个字符串模板作为 Vue 实例的标识使用。模板将会 替换 挂载的元素,挂载元素的内容都将被忽略。
<div id="app"></div>
const vm = new Vue({
el: '#app',
template: `
<div id="ceshi">xxx</div>
`,
})
Vue初始化到挂载的流程
9.vue的生命周期
每个 Vue 实例在被创建时都要经过一系列的初始化过程,例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
(1)生命周期图示
(2)生命周期钩子
所有的生命周期钩子自动绑定 this 上下文到实例中,因此你可以访问数据,对属性和方法进行运算
beforeCreate
在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
<div id="app">
<div @click="handleClick">点击事件</div>
</div>
const vm = new Vue({
el: '#app',
data: {
msg: 'hellow world',
},
beforeCreate () {
console.log(this.msg); // undefined
console.log(this.handleClick); // undefined
console.log('-----beforeCreate-----');
},
methods: {
handleClick () {
console.log(handleClick);
}
},
watch: {
msg: {
handler () {
console.log('侦听msg的值');
},
immediate: true,
}
}
})
打印顺序:
undefined
undefined
-----beforeCreate-----
侦听msg的值
created
在实例创建完成后被立即调用。
在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。
如果要在第一时间调用methods中的方法,或者操作data中的数据,可在此钩子中进行操作。
需要注意的是,执行此钩子时,挂载阶段还未开始,$el 属性目前不可见。
此时,可以进行数据请求,将请求回来的值赋值给data中的数据。
<div id="app">
<div @click="handleClick">点击事件</div>
</div>
const vm = new Vue({
el: '#app',
data: {
msg: 'hellow world',
},
created () {
console.log(this.msg); // hello world
console.log(this.handleClick); // function () {...}
console.log(this.$el); // undefined
console.log('----------created-------');
},
methods: {
handleClick () {
console.log(handleClick);
}
},
watch: {
msg: {
handler () {
console.log('侦听msg的值');
},
immediate: true,
}
}
})
打印顺序:
侦听msg的值
hellow world
ƒ handleClick () { console.log(handleClick); }
undefined
----------created-------
beforeMount
在挂载开始之前被调用,此时模板已经编译完成,只是未将生成的模板替换el对应的元素。
在此钩子函数中,可以获取到模板最初始的状态。
此时,可以拿到vm.$el,只不过为旧模板
const vm = new Vue({
el: '#app',
beforeMount () {
console.log(this.$el);
}
})
mounted
el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。
在该钩子函数中的vm.$el为新模板。
执行完该钩子函数后,代表实例已经被完全创建好。
如果要在第一时间,操作页面上的dom节点时,可以在此钩子函数中操作
const vm = new Vue({
el: '#app',
mounted () {
console.log(this.$el);
}
})
beforeUpdate
数据更新时调用,发生在虚拟 DOM 打补丁之前。此时数据已经更新,但是DOM还未更新
<div id="app">
{{ msg }}
</div>
const vm = new Vue({
el: '#app',
data: {
msg: 'hellow world',
},
beforeUpdate () {
console.log(this.msg);
console.log(this.$el);//这里会打印出更新后的,因为this.$el是引用类型,这里要看更新前的,要写成this.$el.outHTML
},
methods: {
handleClick () {
console.log('handleClick');
}
}
})
this.msg = 'xxx';
updated
数据更改导致DOM重新渲染后,会执行该钩子函数。
此时数据和dom同步。
beforeDestroy
实例销毁之前调用。在这一步,实例仍然完全可用。
可以在该钩子函数中,清除定时器。
<div id="app">
{{ msg }}
</div>
const vm = new Vue({
el: '#app',
data: {
msg: 'hellow world',
timer: 0,
},
created () {
this.timer = setInterval(() => {
console.log('xxx');
}, 500)
},
beforeDestroy () {
clearInterval(this.timer);
}
})
destroyed
Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除。
10.组件
组件是可复用的Vue实例,且带有一个名字。
可以在一个通过new Vue创建的根实例中,把这个组件作为自定义元素来使用:
<div id="app">
<zj-cmp></zj-cmp>
</div>
const vm = new Vue({
el: '#app'
})
因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项(选项可以通过$option来获取),例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。
(一)组件注册
①全局组件注册
Vue.component('name',{各种选项})
- 参数:
参数1:{string}
参数2:{Function | Object} [definition]
例:
<div id="app">
<button-counter></button-counter>
</div>
<script>
Vue.component('button-counter', {
data () {
return {
count: 0,
}
},
template: `
<button @click="count ++">你按了我{{ count }}次</button>
`
})
const vm = new Vue({
el: '#app',
})
</script>
②局部组件
在components选项中定义要使用的组件。
对于 components 对象中的每一个属性来说,其属性名就是自定义元素的名字,其属性值就是这个组件的选项对象。
<div id="#app">
<button-counter></button-counter>
</div>
<script>
const buttonCounter = {
data () {
return {
count: 0
}
},
template: `
<button @click="count ++">你按了我{{ count }}次</button>
`,
}
const vm = new Vue({
el: '#app',
components: {
'button-counter': buttonCounter
}
})
</script>
③组件名 书写规范
定义组件名的方式有两种:
- 其一:使用kebab-case (横短线分隔命名)(推荐)
//定义组件
Vue.component('my-component', {/***/});
//使用组件
<my-component></my-component>
- 其二:使用PascalCase (大驼峰命名)
//定义组件
Vue.component('MyComponent', {/***/});
//使用组件
<my-component-name> 和 <MyComponentName>都是可接受的
**注意,尽管如此,直接在 DOM (即字符串模板或单文件组件) 中使用时只有 kebab-case 是有效的;因为在DOM中使用大驼峰式命名,浏览器会自动将大写转化为小写,这样对导致出错。**
④组件中的data选项
//组件中的data必须写成函数形式,因为组件是可以进行复用的,只有将其写成函数形式,每一次调用都能生成新的数据,不会因为重复使用导致数据发生改变。
data(){
return {
count:0,
}
}
⑤单根元素
每个组件必须只有一个根元素,当模板的元素大于1时,可以将模板的内容包裹在一个父元素内。
(二)Prop
①注册自定义特性
组件默认只是写好结构、样式和行为,使用的数据应由外界传递给组件。
如何传递?注册需要接收的prop,将数据作为一个自定义特性传递给组件。
如:
<div id="app">
<video-item
title="羊村摇"
poster="https://developer.duyiedu.com/bz/video/955bac93ccb7f240d25a79b2ff6a9fdbda9537bc.jpg@320w_200h.webp"
play="638000"
rank="1207"
></video-item>
</div>
Vue.component('video-item', {
props: ['title', 'poster', 'play', 'rank'],
template: `<div>{{ title }}</div>`
})
上例在使用组件时,给组件传入参数,给组件定义的特性,组件中可以将接收到的数据应用到template模板中。
②Prop的大小写
HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。故:当 传递的prop为 短横线分隔命名时,组件内 的props 应为 驼峰命名 。
如:
<div id="app">
<!-- 在 HTML 中是 kebab-case 的 -->
<video-item sub-title="hello!"></video-item>
</div>
Vue.component('video-item', {
// 在 JavaScript 中是 camelCase 的
props:["subTitle"],
template:"<h3>{{subTitle}}</h3>"
})
要注意的是:如果使用的是字符串模板,那么这个限制就不存在了。
③传递静态或动态 Prop
像这样,我们已经知道了可以给 prop 传入一个静态的值:
<video-item title="羊村摇"></video-item>
若想要传递一个动态的值,可以配合v-bind指令进行传递,如:
<video-item :title="title"></video-item>
传递一个对象的所有属性
如果你想要将一个对象的所有属性都作为 prop 传入,你可以使用不带参数的 v-bind 。例如,对于一个给定的对象 person:
person: {
name: 'shanshan',
age: 18
}
//传递全部属性
<my-component v-bind="person"></my-component>
上述代码等价于:
<my-component
:name="person.name"
:age="person.age"
></my-component>
④组件_Prop验证
我们可以为组件的 prop 指定验证要求,例如你可以要求一个 prop 的类型为什么。如果说需求没有被满足的话,那么Vue会在浏览器控制台中进行警告,这在开发一个会被别人用到的组件时非常的有帮助。
为了定制 prop 的验证方式,你可以为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组。例如:
Vue.component('my-component',{
props:{
tilte:String,
likes:Number,
ispublish:Boolean,
commentIds:Array,
author:Object,
callback:Function,
contactsPromise:Promise
}
})
上述代码中,对prop进行了基础的类型检查,类型值可以为下列原生构造函数中的一种:String
、Number
、Boolean
、Array
、Object
、Date
、Function
、Symbol
、任何自定义构造函数、或上述内容组成的数组。
需要注意的是null
和 undefined
会通过任何类型验证。
除基础类型检查外,我们还可以配置高级选项,对prop进行其他验证,如:类型检测、自定义验证和设置默认值。
例如:
Vue.component("my-component",{
prop:{
title:{
type:String,// 检查 prop 是否为给定的类型
default:'XXX',// 为该 prop 指定一个默认值,对象或数组的默认值必须从一个工厂函数返回,如:default () { return {a: 1, b: 10} },
required:true,// 定义该 prop 是否是必填项
validtor(prop){ // 自定义验证函数,该prop的值回作为唯一的参数代入,若函数返回一个false的值,那么就代表验证失败
reutnr prop.length<140;
}
}
}
})
⑤组件_单向数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
- 这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用,在后续操作中,会将这个值进行改变。在这种情况下,最好定义一个本地的 data 属性并将这个 prop 用作其初始值:
props:['initialCounter'],
data(){
return {
counter:this.initialCounter,
}
}
- 这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:
prop:['size'],
computed:{
normalizdSize:funciton(){
return this.size.trim().toLowerCase()
}
}
⑥组件_非prop特性
非prop特性指的是,一个在组件内部未被注册的特性,即在使用组件时,传递了特性,但是未在定义组件的内部对其进行定义,那么该特性将会被添加到这个组件的根元素上。
替换/合并已有的特性
想象一下 <my-cmp>
的模板是这样的:
<input type="date" class="b">
为了给我们的日期选择器插件定制一个主题,我们可能需要像这样添加一个特别的类名:
<my-cmp
class="my-cmp"
></my-cmp>
在这种情况下,我们定义了两个不同的 class 的值:
- my-cmp,这是在组件的模板内设置好的
- b,这是从组件的父级传入的
对于绝大多数特性来说,从外部提供给组件的值会替换掉组件内部设置好的值。所以如果传入 type=“text” 就会替换掉 type=“date” 并把它破坏!庆幸的是,class 和 style 特性会稍微智能一些,即两边的值会被合并起来,从而得到最终的值:my-cmp b。
禁用特性继承
如果不希望组件的根元素继承特性,那么可以在组件选项中设置 inheritAttrs: false。如:
Vue.component('my-cmp',{
inheritAttrs:false,
})
这样组件的根元素就不会继承特性。
在这种情况下,非常适合去配合实例的 $attrs 属性使用,这个属性是一个对象(键名为传递的特性名,键值为传递特性值,这些都是写在调用组件时标签中的特性)。
{
required:true,
placeholder:'enter your username',
}
使用 inheritAttrs: false
和$attrs
相互配合,我们就可以手动决定这些特性会被赋予哪个元素。如:
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>
`,
})
⑦组件_监听组件事件
$emit(‘事件名’)
首先写一个博文组件:
Vue.component('blog-post',{
props:{
post:{
type:Object,
}
},
template:' <div class="blog-post">
<h3>{{ post.title }}</h3>
<button>放大字号</button>
<div>{{ post.content }}</div>
</div>',
})
<div id="app">
<div :style="{fontSize: postFontSize + 'em'}">
<blog-post
v-for="post in posts"
:key="post.id"
:post="post"
>
</blog-post>
</div>
</div>
const vm = new Vue({
el: '#app',
data: {
posts: [
{ title: '标题1', content: '正文内容', id: 0, },
{ title: '标题2', content: '正文内容', id: 1, },
{ title: '标题3', content: '正文内容', id: 2, },
],
postFontSize: 1
}
})
可以看到每一个博文组件中,都有一个按钮,可以去放大页面中字体的字号,也就是说,当点击这个按钮时,我们要告诉父组件改变postFontSize
数据去放大所有博文的文本。碰见这样的情况,该如何做呢?
Vue 实例提供了一个自定义事件来解决这个问题。父组件可以像处理原生DOM元素一样,通过 v-on
指令,监听子组件实例的任意事件,如:
<div id="app">
<div :style="{fontSize: postFontSize + 'em'}">
<blog-post
...
@enlarge-text="postFontSize += 0.1"
>
</blog-post>
</div>
</div>
那么,怎么样能够去监听到一个 enlarge-text
这么奇怪的事件呢?这就需要在组件内,去主动触发一个自定义事件了。
如何触发?
通过调用 $emit 方法 并传入事件名称来触发一个事件,如:
Vue.component('blog-post', {
props: {
...
},
template: `
<div class="blog-post">
...
<button @click="$emit('enlarge-text')">放大字号</button>
...
</div>
`,
})
这样,父组件就可以接收该事件,更新数据 pageFontSize
的值了。
$emit(‘事件名’,抛出的值) 使用事件抛出一个值,用$event接收数值
在有些情况下,我们可能想让 <blog-post>
组件决定它的文本要放大多少。这是可以使用 $emit 的第二个参数来提供这个值,如:
Vue.component('blog-post', {
props: {
...
},
template: `
<div class="blog-post">
...
<button @click="$emit('enlarge-text', 0.2)">放大字号</button>
...
</div>
`,
})
在父组件监听这个事件时,可以通过 $event 访问到被抛出的这个值:
<div id="app">
<div :style="{fontSize: postFontSize + 'em'}">
<blog-post
...
@enlarge-text="postFontSize += $event"
>
</blog-post>
</div>
</div>
未完待续。。。