Vue介绍

Vue是一套基于MVVM设计模式用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。

Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。

MVVM设计模式

在学习认识MVVM设计模式之前,我们先来了解下MVVM的发展历史:

1995年,JavaScript在浏览器上首次设计实现。之后,浏览器就可以通过JavaScript,对页面进行一些修改。如操作HTML的DOM结构和修改CSS来实现一些动画效果。自此JavaScript经历了若干发展阶段:

  • 阶段一:直接用JavaScript操作DOM节点,使用浏览器提供的原生API
  • 阶段二:为了便捷开发并考虑浏览器兼容问题,jQuery以“write Less,Do More”宗旨迅速占领市场:
  • 阶段三:配合服务器端实现MVC设计模式
  • 现在:随着前端页面越来越复杂,用户对于交互性要求也越来越高,散乱的代码将使项目变得难以维护,仅仅用jQuery是远远不够的。MVVM模型应运而生。

MVVM本质上就是MVC 的改进版最早由微软提出,它立足于原有MVC架并且把WPF的新特性糅合进去,以应对客户日益复杂的需求变化。在前端页面中,把Model用纯JavaScript对象表示,View负责显示,两者做到了最大限度的分离。

认识MVC

MVC全名是Model View Controller,是模型(model)视图(view)控制器(controller) 的缩写,一种软件设计典范:

MVC设计模式.png

在MVC中,Controller完全把Model和View进行了分离,主要的程序逻辑在Controller里实现。而且,Controller与具体的View是没有直接关联的,而是通过定义好的接口进行交互,从而使得在变更View时候可以保持Controller的不变,即重用! 不仅如此,我们还可以编写测试用的View,模拟用户的各种操作,从而实现对Controller的测试–而不需要使用自动化的测试工具。 我们甚至可以在Model和View都没有完成时候,就可以通过编写Mock Object(即实现了Model和View的接口,但没有具体的内容的)来测试Presenter的逻辑。
在MVC里,应用程序的逻辑主要在Controller来实现,其中的View是很薄的一层。

在这个过程中,View是很简单的,能够把信息显示清楚就可以了。并且View绝不容许直接访问Model(注意:这个规范其实是MVP设计模式不是MVC)。

认识MVVM

MVVM全名是Model View ViewModel,是模型(model)视图(view)视图模型(view-model) 的缩写,其实 MVVM真实应该叫做MVCVM,MVVM设计模式的出现就是为了解决当今数据解析问题

数据解析问题:

早期MVC年代前端应用的数据往往都比较简单,所以数据解析工作也非常简单。而如今前端应用功能越来越复杂,数据结构也越来越复杂,所以数据解析也就没那么简单了。

如果我们继续按照MVC的设计思路,因为V和M层已经被明确定义不能承担数据解析功能,所以数据解析的部分只能放到了Controller里面。

而今前端复杂数据结构也导致Controller负责数据解析后就将变得相当臃肿。并且相当重要的一点:Controller被设计出来并不是处理数据解析的:

  1. 控制器从视图获取事件和输入;
  2. 管理自己的生命周期;
  3. 实现Controller容器;

所以“数据解析”也不应该由Controller来完成。

因为我们的MVC中,M、V、C都不应该处理数据解析。程序开发人员就专门为数据解析创建出了一个新的类:ViewModel。

这种模式跟经典的MVC模式很相似,使用数据绑定技术为View量身定制model,这个model就是ViewModel。ViewModel包含所有由UI特定的接口和属性,并由一个 ViewModel 的视图的绑定属性,并可获得二者之间的松散耦合,所以需要在ViewModel 直接更新视图中编写相应代码。

在MVVM中,Controller不再直接持有Model。而是由VM层直接持有Model并进行数据解析并把处理的结果给Controller。因为Controller只需要数据解析的结果而不关心过程,所以就相当于VM把“解析Model”给封装起来,而C甚至根本就不需要知道M的存在就能工作。那么MVVM中的持有关系就是:C持有VM,VM持有M

这样Controller需要的数据由VM全权负责,所以VM层的Controller存在感被完全的降低这样MVCVM模式就被成为MVVM设计模式

Vue的安装

  • 方法一: 在html 文件中引入vue.js
1
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  • 方法二: 通过npm下载vue模块
1
npm install -s vue
  • 方法三: 通过Vue 官方脚手架 vue-cli 搭建vue组件化项目
1
2
3
4
#全局安装 vue-cli 环境变量中
npm install -g @vue/cli
# 使用 vue-cli指令搭建单页面应用项目
vue create hello-world

起步实例化一个Vue对象

介绍: Vue会为开发者提供一个名叫Vue的构造函数,开发人员可以通过new关键字创建Vue构造函数的实例对象(MVVM中VM层)。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--引入Vue三方库-->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<!--创建一个DOM元素-->
<div id="app"></div>

<script>
// 通过new关键字实例化一个Vue对象
// Vue 构造函数接收配置对象
new Vue({
el: '#app' // el 指定页面中DOM元素,Vue就会以这个DOM为挂载对象
})
</script>

Vue实例配置选项

在实例化Vue对象时,构造函数Vue可以接受一个Object作为实例配置对象,该对象中接受多个配置选项用来实现不同的效果。

el: 提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。可以是 CSS 选择器,也可以是一个 HTMLElement 实例。挂载完毕后当前DOM内部渲染将会由当前Vue实例对象所管理与控制

1
2
3
new Vue({
el: '#app'
})

data: (属性)存放当前Vue实例对象数据的配置。Vue 将会递归的将 data 的 property 转换为 getter/setter,从而让 data 的 property 能够响应数据变化

对象必须是纯粹的对象 (含有零个或多个的 key/value 对): 浏览器 API 创建的原生对象,原型上的 property 会被忽略。大概来说,data 应该只能是数据,不推荐观察拥有状态行为的对象。

注意: data 对象自身可以被实例对象的$data属性所访问,并且在所有data直接子属性都可以被实例对象直接访问和修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var vm = new Vue({
el: '#app',
data: {
name: '小明',
age: 18,
detail: {
tel: 138121345678,
eMail: '[email protected]',
address: '广州'
}
},
mounted() {
console.log(this.age) // 18 实例对象可以直接访问
}
})

vm.$data // data对象
vm.age //18 等价于 vm.$data.age
vm.detail.tel //138121345678 vm.$data.detail.tel

// 实例对象的方法可以直接修改
// 并且vm的data数据改变会引起页面的自动更新(数据的双向绑定)
vm.age++
vm.detail.tel = 13193870000

数据绑定技术

概念: Vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的,Vue在初始化使用Object.defineProperty递归的将data的属性添加一个getter/setter (监听器Observer),用来劫持并监听所有属性(Vue 解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器即调用当前属性getter方法的元素就是订阅者)。Vue会把所有当前data的订阅者存放在一个dep名单中,如果有变动的,就通过dep名单通知所有订阅者。从而更新视图。

vue双向绑定图示.png

模板语法

双大括号文本差值

概念: Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统:vue使用 {{js表达式}} (双大括号语法) 将实例中的属性或者其他js表达式插值绑定到模板的任何文本节点

语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div id="app">
内部都由Vue实例对象管理
<p>姓名:{{name}}</p>
<p>年龄:{{age}}</p>
<p>状态:{{age >= 18 ? '已成年':'未成年'}}</p>
</div>

<script>
let vm = new Vue({
el: '#app',
data: {
name: '小明',
age: 18
}
})
</script>

注意:
双大括号语法会将内部的表达式以纯文本的形式插入到对应节点内部。这种模式可以预防xss攻击(注入攻击将一段恶意脚本发送到html页面从而获取用户的cookie信息)

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="app">
<p>{{script}}</p>
// 这里渲染的结果是'<div>hello world<div>'的文本节点,而不是一个dom元素
</div>

<script>
let vm = new Vue({
el: '#app',
data: {
script: '<div>hello world<div>'
}
})
</script>

指令语法

概念: Vue为开发者提供很多执行应用在虚拟DOM标签上以实现不同的效功能效果

语法: 指令=“js表达式”

  • 指令 v-html

介绍: 可以将文本以html形式插入到指定节点内部而不用向上面一样插入一个纯文本。

注意: 永远不要把这个方法暴露给用户

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="app">
<p>{{script}}</p> // 纯文本
<div v-html="script"></div> // h3dom元素
</div>

<script>
let vm = new Vue({
el: '#app',
data: {
script: '<h3>hello world</h3>'
}
})
</script>
  • 指令 v-once

介绍: 一次性地插值,当数据改变时,插值处的内容不会更新

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="app">
<p v-once="str"></p> // v-once插入的文本内容不会更新
</div>

<script>
let vm = new Vue({
el: '#app',
data: {
str: 'hello world'
}
})
setTimeout(() => vm.str = 'hello Vue !')
</script>

v-bind属性差值

概念: 使用指令 v-bind:属性 =“js表达式” 形式将实例中的data或者其他js表达式插值绑定到标签的任何属性节点中

语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="app">
已成年<input type="radio" v-bind:checked="age >= 18"/>
未成年<input type="radio" v-bind:checked="age < 18"/>
</div>

<script>
let vm = new Vue({
el: '#app',
data: {
age: 18,
}
})
</script>

注意:

  1. v-bind:指令可以简写成一个冒号 “:”
1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="app">
已成年<input type="radio" :checked="age >= 18"/>
未成年<input type="radio" :checked="age < 18"/>
</div>

<script>
let vm = new Vue({
el: '#app',
data: {
age: 18,
}
})
</script>
  1. v-bind 支持动态属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div id="app">
<a v-bind:[attrname]="link"> 百度 </a>
<!-- <a src="http://www.baidu.com"> 百度 </a> -->
</div>

<script>
let vm = new Vue({
el: '#app',
data: {
attrname: 'src',
link: 'http://www.baidu.com'
}
})
</script>

注意:

  1. 动态参数预期会求出一个字符串,异常情况下值为 null。这个特殊的 null 值可以被显性地用于移除绑定。任何其它非字符串类型的值都将会触发一个警告。
  2. 空格和引号,放在 HTML attribute 名里是无效的
1
<a v-bind:['foo' + bar]="value"> ... </a> // 错误

v-bind绑定class

1. 对象语法

我们观察下面代码,我们给每一段歌词元素都绑定了一个class类。内部js表达式逻辑就是当currentIndex 与歌词下标匹配时,歌词元素的class值就会变成active,否则为空。但是我们发现,代码过于繁琐。Vue针对class提供对象语法来简化下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<div id="app">
<p v-bind:class="currentIndex === 0 ? 'active': ''">
第一段歌词 0
</p>
<p v-bind:class="currentIndex === 1 ? 'active': ''">
第二段歌词 1
</p>
<p v-bind:class="currentIndex === 2 ? 'active': ''">
第三段歌词 2
</p>
<p v-bind:class="currentIndex === 3 ? 'active': ''">
第四段歌词 3
</p>
<p v-bind:class="currentIndex === 4 ? 'active': ''">
第五段歌词 4
</p>
</div>

<script>
let vm = new Vue({
el: '#app',
data: {
currentIndex: 3
}
})
</script>

v-bind绑定的class支持对象写法: v-bind:class = {class名: 判别式}。当判别式为真时,保留该类名否则删除该类名

上面的代码可以使用class对象语法简写为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div id="app">
<p v-bind:class="{active: currentIndex === 0}">第一段歌词 0</p>
<p v-bind:class="{active: currentIndex === 1}">第二段歌词 1</p>
<p v-bind:class="{active: currentIndex === 2}">第三段歌词 2</p>
<p v-bind:class="{active: currentIndex === 3}">第四段歌词 3</p>
<p v-bind:class="{active: currentIndex === 4}">第五段歌词 4</p>
</div>

<script>
let vm = new Vue({
el: '#app',
data: {
currentIndex: 3
}
})
</script>

2. 数组语法

概念: Vue还支持v-bind:class=数组写法,数组中的每一项都可以是js表达式,并且数组中可以包含class对象写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<div id="app">
<p v-bind:class="currentIndex === 0 ? 'active lry ' + className: 'lry '+ className">
第一段歌词 0
</p>
<p v-bind:class="[{active: currentIndex === 1}, 'lry', className]">
第二段歌词 1
</p>
<p v-bind:class="[{active: currentIndex === 2}, 'lry', className]">
第三段歌词 2
</p>
<p v-bind:class="[{active: currentIndex === 3}, 'lry', className]">
第四段歌词 3
</p>
<p v-bind:class="[{active: currentIndex === 4}, 'lry', className]">
第五段歌词 4
</p>
</div>

<script>
let vm = new Vue({
el: '#app',
data: {
currentIndex: 3,
className: 'test'
}
})
</script>

注意:

  1. class 的数组语法,对象语法中的数组或对象都可以存放data中绑定给class (data中不要使用this)
  2. 一个dom元素中可以最对同时拥有 一个绑定的class属性和一个普通class属性
1
2
3
4
5
6
<p v-bind:class="[{active: currentIndex === 3}, 'lry', className]">
第四段歌词 3
</p>
<p class="lry" v-bind:class="[{active: currentIndex === 4}, className]">
第五段歌词 4
</p>

v-bind绑定style

1. 对象语法

概念: v-bind:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div id="app">
<!-- 短横线命名CSS样式需要加引号-->
<p v-bind:style="{color,backgroundColor,'font-size': '18px'}">
我是一段文本
</p>
</div>

<script>
new Vue({
el: '#app',
data: {
color: 'blue',
backgroundColor: 'orange'
}
})
</script>

2. 数组语法

概念: v-bind:style 的数组语法可以将多个样式对象应用到同一个元素上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="app">
<p v-bind:style="[defaultStyle,{color,backgroundColor,fontSize: '18px'}]">
我是一段文本
</p>
</div>

<script>
new Vue({
el: '#app',
data: {
color: 'blue',
backgroundColor: 'orange',
defaultStyle: {
fontWeight: 700,
border: '1px solid #ccc'
}
}
})
</script>

3.v-bind:style的多重值

概念: style 绑定中的样式属性可以提供一个包含多个值的数组,常用于提供多个带前缀的值。

1
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

这样写只会渲染数组中最后一个被浏览器支持的值。在本例中,如果浏览器支持不带浏览器前缀的 flexbox,那么就只会渲染 display: flex。

4.v-bind:style自动添加前缀

概念: 当v-bind:style 使用需要添加浏览器引擎前缀的 CSS property 时,如 transform,Vue.js 会自动侦测并添加相应的前缀。

1
<div :style="{  transform: 'rotate(-45deg)' }">Hello</div>

事件处理

概念: 在vue中可以给DOM元素通过 v-on 指令,绑定事件处理逻辑。

语法: v-on:事件名="js表达式"

1
2
3
4
5
6
7
8
9
10
11
12
13
 <div id="app">
每次点击h2标签 count都会加一
<h2 v-on:click="count++">{{count}}</h2>
</div>

<script>
new Vue({
el: '#app',
data: {
count: 17
}
})
</script>

注意: 不应该使用箭头函数来定义 method 函数 (例如 plus: () => this.a++)。理由是箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向 Vue 实例,this.a 将是 undefined。

扩展: v-on: 指令可以简写为 @ 符号

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="app">
<h2 v-on:click="count++">{{count}}</h2>
<button @click="count--">minus</button>
</div>

<script>
new Vue({
el: '#app',
data: {
count: 17
}
})
</script>

Vue实例配置methods选项

在学习Vue事件绑定函数之前,我们需要学习如何在Vue实例中创建实例对象自身的方法函数。创建Vue实例对象的方法同样需要通过Vue实例配置选项来创建。

methods实例配置选项

语法{ [key: string]: Function }

介绍:methods配置对象将被混入到 Vue 实例中作为当前实例的方法。 VM 实例可以直接访问该对象内的方法,或者在指令表达式中使用他们。methods对象中的方法的 this 会自动绑定为 Vue 实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- 在指令表达式中使用-->
<div id='app'>{{reverseStr()}}</div>

<script>
var vm = new Vue({
el: '#app',
data: {
a: 1 ,
str: 'hello world'
},
methods: {
addA: function () {
this.a++
},
reverseStr() { // ES6写法
return this.str.split('').reverse().join('')
}
}
})
vm.addA() // 通过Vue实例对象直接访问methods内部的方法
vm.a // 2
</script>

Vue事件绑定函数

介绍: 在Vue的v-on事件绑定语法,支持将函数作为js表达式绑定到监听事件中。当目标元素的事件被触发时绑定的函数将会被执行。

注意:

  1. v-on事件绑定中js表达式传入函数,函数名后不显式添加()会被vue隐式默认添加(),并且会默认的将event原生事件对象作为参数传入到函数中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="app">
<!-- 被点击会打印undefined,因为函数显示被调用并没有向函数内部传递任何参数-->
<button @click="clickHandel()">click me</button>
<!-- 被点击会打印事件对象event,vue会隐式调用该函数并传递event作为当前函数的参数-->
<button @click="clickHandel">click me</button>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<script>
new Vue({
el: '#app',
methods: {
clickHandel(event) {
console.log(event)
}
}
})
</script>
  1. 如果需要在事件绑定函数中不但手动传参还要传递event事件对象,需要使用vue实例对象提供的特殊变量 $event ,将事件对象手动传递给函数内部
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div id="app">
// 向函数手动传递参数 $event是vue所提供的当前原生事件对象特殊变量
<button @click="clickHandel($event,12)">click me</button>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<script>
new Vue({
el: '#app',
methods: {
clickHandel(event,num) {
console.log(event,num)
}
}
})
</script>

修饰符

概念: 在事件处理程序中用 event.preventDefault()event.stopPropagation() 是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符。简化上面这种DOM事件细节需求

语法: v-on:事件名.修饰符1.修饰符2...

事件修饰符

.stop 阻止事件冒泡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="app">
<div :style="{width: '200px',height: '200px',backgroundColor: 'red'}" @click="clickHandle($event,'div')">
<p :style="{width: '120px',height: '120px',backgroundColor: 'green'}"
@click.stop="clickHandle($event,'p')">
<a @click.stop="clickHandle($event,'a')">我是A标签</a>
</p>
</div>
</div>

<script>
new Vue({
el: '#app',
methods: {
clickHandle(event, detail) {
console.log(detail + '被点击了', event.target)
}
}
})
</script>

.prevent 阻止事件浏览器默认行为

1
2
3
<form>
<input @click.prevent="clickHandle($event,'submit')" type="submit">
</form>

.capture 事件使用捕获

1
2
3
4
5
6
7
<div :style="{width: '200px',height: '200px',backgroundColor: 'red'}" 
@click.capture="clickHandle($event,'div')">
<p :style="{width: '120px',height: '120px',backgroundColor: 'green'}"
@click.capture="clickHandle($event,'p')">
<a @click="clickHandle($event,'a')">我是A标签</a>
</p>
</div>

.self event.target必须是绑定事件的元素自身才能触发

1
2
3
4
5
6
7
<div :style="{width: '200px',height: '200px',backgroundColor: 'red'}" 
@click="clickHandle($event,'div')">
<p :style="{width: '120px',height: '120px',backgroundColor: 'green'}"
@click.self="clickHandle($event,'p')">
<a @click="clickHandle($event,'a')">我是A标签</a>
</p>
</div>

.once 事件只触发一次

1
2
3
4
5
6
7
<div :style="{width: '200px',height: '200px',backgroundColor: 'red'}" 
@click="clickHandle($event,'div')">
<p :style="{width: '120px',height: '120px',backgroundColor: 'green'}"
@click.self.stop.once="clickHandle($event,'p')">
<a @click="clickHandle($event,'a')">我是A标签</a>
</p>
</div>

.passive 提高移动端scroll事件滑动性能的修饰符,该修饰符不能与 .prevent 一起使用

1
2
3
4
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>

按键修饰符

概念: 在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符

修饰符种类:

  • KeyboardEvent.key修饰符

介绍: Vue支持 键盘事件中每个键的key按键名转换为 kebab-case 来作为修饰符。

语法v: -on:事件名.[event.key]

1
2
3
4
5
6
// 按键盘page down键触发
<textarea @keyup.page-down="keyUpHandle"></textarea>
// 按键盘a触发
<textarea @keyup.a="keyUpHandle"></textarea>
// 按键盘a或者s触发
<textarea @keyup.a.s="keyUpHandle"></textarea>

注意: 为了在必要的情况下支持旧浏览器,Vue 提供了绝大多数常用的按键码的别名

修饰符 描述
.enter 回车键
.tab Tab键
.delete 捕获“删除”和“退格”键
.esc ESC键
.space 空格键
.up 方向键上
.down 方向键下
.left 方向键左
.right 方向键右
  • 系统修饰键

介绍: 系统修饰键可以配合其他按键一起按下相应按键时才触发鼠标或键盘事件的监听器。

语法: v-on:事件名.系统修饰键.[event.key]

修饰符 描述
.ctrl ctrl键
.alt alt键
.shift shift键
.meta 在 Mac 系统键盘上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞)。在 Sun 操作系统键盘上,meta 对应实心宝石键 (◆)。在其他特定键盘上,尤其在 MIT 和 Lisp 机器的键盘、以及其后继产品,比如 Knight 键盘、space-cadet 键盘,meta 被标记为“META”。在 Symbolics 键盘上,meta 被标记为“META”或者“Meta”。
1
2
3
4
5
6
// 在按下shift键后再按其他键才会触发
<textarea @keyup.shift="keyUpHandle"></textarea>
// 在按下shift键后按a键才会触发
<textarea @keyup.shift.a="keyUpHandle"></textarea>
// 在按下shift键ctrl键后再按a键才会触发
<textarea @keyup.shift.ctrl.a="keyUpHandle"></textarea>

注意: vue在2.5.0 新增 .exact

介绍: 修饰符(严格修饰符),该修饰符的功能是,严格控制系统修饰键按压

1
2
3
4
5
// 非严格模式下在按下shift键ctrl键后再按a键也会触发
<textarea @keyup.shift.a="keyUpHandle"></textarea>
// 严格模式下在按下shift键ctrl键后再按a键不会触发
// 只有仅按shift键没有按下其他系统修饰键的情况下按a才会触发
<textarea @keyup.shift.a.exact="keyUpHandle"></textarea>
  • 鼠标按钮修饰符

概念: 鼠标修饰符会限制处理函数仅响应特定的鼠标按钮。

修饰符 描述
.left 鼠标左键
.right 鼠标右键
.middle 鼠标中间滚轮键

条件渲染

指令 v-if

概念: 允许使用 v-if / v-else / v-else-if 控制元素的渲染
语法: <元素 v-if="判别式" />

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div id="app">
<div
:style="{width: '100px',height: '100px', backgroundColor: 'red'}"
v-if="show"
></div>
<button @click="show = !show">show/hidden</button>
</div>

<script>
new Vue({
el: '#app',
data: {
show: true
}
})
</script>

语法: <元素1 v-if="判别式" /> <元素2 v-else />

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div id="app">
<div
:style="{width: '100px',height: '100px', backgroundColor: 'red'}"
v-if="show"
>白天</div>
<!--v-if元素 与 v-else元素之间除了空行和注释以外不能有其他元素否则会报错-->
<div :style="{width: '100px',height: '100px', backgroundColor: '#ccc'}" v-else>
晚上
</div>
<button @click="show = !show">show/hidden</button>
</div>

<script>
new Vue({
el: '#app',
data: {
show: true
}
})
</script>

语法: <元素1 v-if="判别式" /> <元素2 v-else-if="判别式" /> ... <元素n v-else />

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div id="app">
<!--注意v-if v-else-if v-else元素之间除了空行和注释以外不能有其他任何节点-->
<div v-if="score > 90">
优秀
</div>
<div v-else-if="score >= 85">
良好
</div>
<div v-else-if="score >= 60">
及格
</div>
<div v-else>
不及格
</div>
</div>

<script>
let vm = new Vue({
el: '#app',
data: {
score: 50
}
})
</script>

指令 v-show

概念: 允许使用 v-show 控制元素的显示隐藏

语法: v-show="判别式"

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="app">
<div v-show="show" style="width: 100px;height: 100px;background-color: red;">show</div>
<button @click="show = !show">show/hidden</button>
</div>

<script>
let vm = new Vue({
el: '#app',
data: {
show: true
}
})
</script>

注意:

  • v-if指令实现元素的隐藏是直接将该元素从DOM节点中删除,而v-show隐藏元素只是设置了元素的display: none属性并没有把元素从DOM节点中移除。因为上面的特性在开发中需要频繁显示隐藏的元素请使用v-show,而只需显示一次的元素请使用v-if
  • Vue 会尽可能高效地渲染元素,通常在使用v-if切换元素时, 会复用已有元素而不是从头开始渲染,下面的例子中元素切换并没有真的卸载掉上一个表单元素重新安装,而是复用了该元素只是修改其placeholder的属性值,在下面的的代码中切换 show 将不会清除用户已经输入到表单的内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div id="app">
<!--请在表单元素中输入内容后切换元素查看效果-->
<input v-if="show" placeholder="请输入密码">
<input v-else placeholder="请输入验证码">
<button @click="show = !show">show/hidden</button>

</div>

<script>
let vm = new Vue({
el: '#app',
data: {
show: true
}
})
</script>
  • Vue提供了一个key属性来表示元素的独立性,简单来说key属性的值是一个字符串。只有在兄弟元素(同一个if语句,同一个循环的同级元素)中key属性相同的元素才可以互相复用。下面的代码中 两个兄弟表单元素因为key属性不同不能互相复用。所以在元素切换时上一个表单元素会被卸载,重新渲染新的表单元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div id="app">
<!--请在表单元素中输入内容后切换元素查看效果-->
<input key="1" v-if="show" placeholder="请输入密码">
<input key="2" v-else placeholder="请输入验证码">
<button @click="show = !show">show/hidden</button>
</div>

<script>
let vm = new Vue({
el: '#app',
data: {
show: true
}
})
</script>

列表渲染

指令 v-for

概念: Vue可以使用户v-for遍历数组或者对象循环渲染多个相同DOM节点

语法: v-for="item in <Array>" item为数组每一项的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 <div id="app">
<ul>
<li v-for="item in arr" :key="item" :class="'item-'+item">{{item}}</li>
</ul>
</div>

<script>
new Vue({
el: '#app',
data: {
arr: ['a', 'b', 'c', 'd']
}
})
</script>

语法: v-for="(item, index) in <Array>" item为数组每一项的值,index为对应项的下标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div id="app">
<ul>
<li v-for="(item,index) in arr" :key="index" :class="'item-'+index">{{item}}</li>
</ul>
</div>

<script>
new Vue({
el: '#app',
data: {
arr: ['a', 'b', 'c', 'd']
}
})
</script>

语法: v-for="value in <Object>" value为数组每一项键值对的value值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="app">
<ul>
<li v-for="val in obj" :key="val">{{val}}</li>
</ul>
</div>

<script>
new Vue({
el: '#app',
data: {
obj: {
name: '小明',
age: 19,
address: '深圳'
}
}
})
</script>

语法: v-for="(value,key) in <Object>" value为数组每一项键值对的value值,key为对应键值对的键名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="app">
<ul>
<li v-for="(val,key) in obj" :key="key">{{key}}:{{val}}</li>
</ul>
</div>

<script>
new Vue({
el: '#app',
data: {
obj: {
name: '小明',
age: 19,
address: '深圳'
}
}
})
</script>

语法: v-for="(value, name, index) in <Object>" value为数组每一项键值对的value值,key为对应键值对的键名,index为对应键值对的下标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="app">
<ul>
<li v-for="(val,key,index) in obj" :key="index" :class="'item-'+index">{{key}}:{{val}}</li>
</ul>
</div>

<script>
new Vue({
el: '#app',
data: {
obj: {
name: '小明',
age: 19,
address: '深圳'
}
}
})
</script>

注意: 当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态(当前列表不会增加,删除,更新顺序)。解决方法,为每一个使用v-for遍历出来的DOM节点添加一个唯一key属性,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素

建议尽可能在使用 v-for 时提供 key attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。key属性不需要做到全局唯一,只需要同一个循环遍历的兄弟元素之间不同就好了

注意: 数组排列或者删除的操作请谨慎使用index作为key,index无论怎么删除下标永远都是从0 到 n,导致会删除掉错误dom节点

更新检测

概念: Vue数据双向绑定的原理是在vue实例初始化时递归的给所有的对象属性绑定getter/setter方法,这样导致了两个问题:

1. 数组的更新,

介绍: 数组的每一项不会被vue递归的绑定get/set,所以在vue中数组通过下标添加或者修改值的时候不会引起页面更新。

注意: Vue 的数组都被做了特殊处理,其中的 push() pop() shift() unshift() splice() sort() reverse()方法,都被进行了包裹二次封装(不是原生js方法),所以调用上面的方法将会触发视图更新

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="app">
<p v-for="(item,index) in arr" :key="index">{{item}}</p>
</div>

let vm = new Vue({
el: '#app',
data: {
arr: ['a','b','c','d']
}
})

vm.arr[1] = 'AAA' // 页面不会更新
vm.arr[4] = 'e' // 页面不会更新

可以引起数组更新视图的方法有三种:

  • 方法一: 调用上面说的数组中的方法
  • 方法二: 使用新数组替换旧数组
  • 方法三: 使用vm.$set 或者Vue.set方法修改数组指定下标的值

语法: vm.$set(数组,index,val) 或者 Vue.set(数组,index,value)

1
2
3
4
5
6
7
8
9
10
11
12
let vm = new Vue({
el: '#app',
data: {
arr: ['a', 'b', 'c', 'd']
}
})
/*
vm.arr[1] = 'AAA' // 页面不会更新
vm.arr[4] = 'e' // 页面不会更新
*/
Vue.set(vm.arr, 1, 'CCCC') // 页面会更新
vm.$set(vm.arr, 4,'eee')// 页面会更新

2. 对象的更新

介绍: 因为在Vue初始化时对象中的所有属性都被添加了get/set,所以对象可以直接通过属性名修改就会引起页面的更新,但是如果在Vue实例化之后给某个对象添加新的属性,这个属性将不包含get/set方法。增加新属性或修改这个新属性就不会引起页面更新。

给对象添加新属性并引起页面更新的方法有两种:

  • 方法一: 新对象替换旧对象
  • 方法二: vm.$set / Vue.set 添加新属性,自动给新属性绑定get set方法

语法: Vue.set(对象,'键名',属性值) 或者 vm.$set(对象,'键名',属性值)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div id="app">
<p v-for="(val,key) in obj" :key="key">{{val}}</p>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
obj: {
name: '老李',
age: 18
}
}
})

vm.$set(vm.obj, 'address', '深圳') // 页面会更新

vm.$set(vm.obj, 'address', '广州') // 页面会更新

vm.obj.address = '佛山' //address 自动添加get set 页面会更新

Vue.set(vm.obj, 'hobby', '吃肉') // 页面会更新
</script>

注意Vue.set / vm.$set只可以给data中的Object对象属性添加新属性,不可以给vm.$data对象自身添加新属性,但可以修改vm.$data已声明属性。

1
2
3
4
5
6
7
8
9
10
var vm = new Vue({
el: '#app',
data: {
a: 1 ,
str: 'hello world'
}
})

vm.$set(vm.$data,'a', 2) // 正确, 可以使用set方法修改data对象已有属性
vm.$set(vm.$data, 'test', 10086) // 错误, 不可以使用set方法向data对象添加新属性

错误信息: Avoid adding reactive properties to a Vue instance or its root $data at runtime - declare it upfront in the data option.

避免在运行时向Vue实例或其根$data添加新性属性,请在data选项中预先声明。即请将data中可能用到的属性在初始化时预先声明。data对象不支持实例化后添加新属性。

动画过渡

单一元素/组件的过渡

概念:Vue 在元素显示隐藏时, 提供了 transition 的封装组件,可以给任何元素和组件添加进入/离开过渡

语法<transition name="过渡css名">单一标签</transition>

被transition标签包裹的元素在其显示隐藏时,六个不同的阶段会自动添加6个class名:

元素进入的第一帧 [transition name 属性]-enter

元素进入中 [transition name 属性]-enter-active

元素进入的最后一帧 [transition name 属性]-enter-to

元素离开的第一帧 [transition name 属性]-leave

元素离开中 [transition name 属性]-leave-active

元素离开的最后一帧 [transition name 属性]-leave-to

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<div id="app">
<!--
被transition标签包裹的元素在其显示隐藏时,六个不同的阶段会自动添加6个class名
元素进入的第一帧 [transition name 属性]-enter
元素进入中 [transition name 属性]-enter-active
元素进入的最后一帧 [transition name 属性]-enter-to
元素离开的第一帧 [transition name 属性]-leave
元素离开中 [transition name 属性]-leave-active
元素离开的最后一帧 [transition name 属性]-leave-to
-->
<transition name="fade">
<div class="box" v-if="show"></div>
</transition>

<button @click="show = !show">show/hidden</button>

</div>

<script>
let vm = new Vue({
el: '#app',
data: {
show: true
}
})
</script>

<style>
.box {
width: 100px;
height: 100px;
background-color: green;
}

.fade-enter, .fade-leave-to{
/*元素进入动画第一帧 类名*/
opacity: 0;
transform: translateX(300px);
width: 0;
height: 0;
}

.fade-enter-active, .fade-leave-active {
/*元素进入整个过程中 类名*/
transition: all .5s linear;
}

.fade-enter-to, .fade-leave {
/*元素进入动画最后一帧 类名*/
opacity: 1;
transform: translateX(0);
width: 100px;
height: 100px;
}
</style>

注意:

  1. transition 标签内只能包含一个直接子元素
  2. transition 标签的name属性可以省略, 省略后元素进入离开时类名默认使用v-开头,例: v-enter v-enter-to v-leave v-leave-to。

列表的进入/离开过渡

概念: 因为transition标签内部只能监听单个节点,或同一时间渲染多个节点中的一个。所以transition标签内部无法监听多个节点的。所以vue提供了 <transition-group> 组件实现内部监听多个节点的进入离开

语法: <transition-group name="过渡css名">多个标签</transition-group>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<div id="app">
<transition-group name="fade">
<p v-for="c in arr" :key="c">{{c}}</p>
</transition-group>
</div>

<script>
let vm = new Vue({
el: '#app',
data: {
arr: ['a', 'b', 'c', 'd', 'e']
}
})
</script>

<style>
.fade-enter,
.fade-leave-to {
transform: translateX(100px);
}

.fade-enter-active,
.fade-leave-active {
transition: all .5s linear;
}

.fade-enter-to,
.fade-leave {
transform: translateX(0);
}
</style>

注意

  • 在transition-group组价中,列表渲染的key不能使用index。index作为key在添加或者删除时会导致不是正确的DOM节点被移除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!--
请观察下面代码,
我们在使用 transition-group 元素时内部兄弟节点使用数组每一项的值作为key ':key="c"' (因为key值必须唯一所以请不要让数组中包含相同的值),
这时请尝试添加或删除数组中的某一项,你胡发现动画效果是正确的
但是如果我们使用数组下标作为key后,元素的添加或删除动画会异常
-->
<div id="app">
<transition-group name="fade">
<p v-for="(c,i) in arr" :key="i">
{{c}}
<button @click="removeArr(c)">remove</button>
</p>
</transition-group>
</div>

<script>
let vm = new Vue({
el: '#app',
data: {
arr: ['a', 'b', 'c', 'd', 'e']
},
methods: {
removeArr(c) {
this.arr = this.arr.filter(a => a !== c)
}
}
})
</script>

<style>
.fade-enter,
.fade-leave-to {
transform: translateX(100px);
}

.fade-enter-active,
.fade-leave-active {
transition: all .5s linear;
}

.fade-enter-to,
.fade-leave {
transform: translateX(0);
}
</style>
  • transition-group会被vue默认渲染成一个span元素,如果你想设置transition-group组件渲染输出的节点可以设置其tag属性,例 tag=“ul” transition-group组件就会被渲染成ul标签
1
2
3
4
5
<transition-group tag="ul" name="move">
<li v-for="todo in todos" :key="todo.id">
{{todo.text}}
</li>
</transition-group>

JavaScript 动画钩子

概念:无论transition 和 transition-group 标签都支持使用动画js钩子。动画的钩子就是元素在整个动画过渡中默写特殊的时间点。当动画到达这些时间点时就会触发js钩子所绑定方法。有了js动画钩子就可以实现一些复杂的动画。

语法:动画钩子就是vue提供的一些自定义事件,监听响应的事件触发绑定的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"

v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"
>
<!-- ... -->
</transition>

<transition-group
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"

v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"
>
<!-- ... -->
</transition-group>

注意:动画钩子事件会将当前动画元素作为参数传入到函数内部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!--
Velocity 和 jQuery.animate 的工作方式类似,也是用来实现 JavaScript 动画的一个很棒的选择
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<div id="app">
<transition @before-enter="beforeEnter" @enter="enter" @after-enter="afterEnter">
<div v-show="show" class="box"></div>
</transition>

<button @click="show = !show">show/hidden</button>
</div>

<script>
console.log(Velocity)
new Vue({
el: "#app",
data: {
show: true
},
methods: {
beforeEnter(el) {
console.log(el, '元素将要显示')
el.style.opacity = 0
},
enter(el, done) {
console.log('元素正在显示')
Velocity(el, {
opacity: "1",
translateY: '30px',
}, {
duration: 400,
easing: "swing",
loop: 2,
complete: done // 动画结束后触发done表示自定义动画结束
});
},
afterEnter(el) {
el.style.opacity = 1
// console.log(el.offsetHeight)
console.log('元素显示完毕')
}
}
})
</script>

注意:动画钩子事件会中enter事件与leave事件除了会默认传递第一个参数el(当前动画元素)以外,还会传递第二个参数 done,done 是一个方法用来告诉Vue当前元素的自定义动画已经结束,动画钩子才会执行下一步 after-enter 或者after-leave。如果自定义动画效果结束后不调用done可能会导致动画元素样式不更新

表单的数据绑定

概念: 在开发中我们经常需要创建一些表单元素,表单元素的内容由vue实例中的data控制。而用户在表单上的输入操作又可以修改实例中的数据。上面的行为我们称之为表单的双向绑定。

例子: 下面的代码就是实现input表单的双向绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="app">
<input v-bind:value="str" @input="inputHandel">
{{str}}
</div>

<script>
new Vue({
el: "#app",
data: {
str: 'hello world'
},
methods: {
inputHandel(evt) {
this.str = evt.target.value
}
}
})
</script>

注意

  • Vue为了简化表单元素的数据绑定, input 、select、 textarea 这些值绑定全部统一使用value属性,但是单复选框依然是checked属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<div id=app>    
<input v-bind:value="str" @input="inputHandel">
{{str}}
<textarea :value="str"></textarea>
<select :value="city">
<option value="bj">北京</option>
<option value="sh">上海</option>
<option value="gz">广州</option>
<option value="sz">深圳</option>
</select>

<input type="checkbox" :checked="true">
</div>

<script>
new Vue({
el: "#app",
data: {
str: 'hello world',
city: 'sz'
},
methods: {
inputHandel(evt) {
this.str = evt.target.value
}
}
})
</script>
  • 虽然Vue帮我们简化了表单元素值得绑定,但是在不同表单元素之间依然有一些差异的。要根据不同的表单元素差异绑定不同的属性与方法还是有些繁琐。这时Vue为了帮我们解决表单的差异,提供了一个v-model的指令v-model本质上是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。

语法 v-model="data属性"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<div id="app">
<input v-model="str">
{{str}}
<textarea v-model="str"></textarea>
<p>{{city}}</p>
<select v-model="city">
<option value="bj">北京</option>
<option value="sh">上海</option>
<option value="gz">广州</option>
<option value="sz">深圳</option>
</select>
<p>{{agree? '同意' : '不同意'}}</p>
<input type="checkbox" v-model="agree">
</div>

<script>
new Vue({
el: "#app",
data: {
str: 'hello world',
city: 'sz',
agree: false
}
})
</script>

注意: v-model 绑定支持表单元素的复选时,v-model绑定的数据请使用数组类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<div id="app">
<!-- 选择器当设置了可复选属性时,会自动将v-model中的数据转化为数组 -->
<select v-model="city" multiple>
<option value="" disabled>请选择你所在的城市</option>
<option value="bj">北京</option>
<option value="sh">上海</option>
<option value="gz">广州</option>
<option value="sz">深圳</option>
</select>

<!--复选框要实现复选时,必须将v-model中的数据设置为数组-->
<p>爱好{{hobbys}}</p>
<label>
抽烟
<input type="checkbox" value="抽烟" v-model="hobbys">
</label>
<label>
喝酒
<input type="checkbox" value="喝酒" v-model="hobbys">
</label>
<label>
烫头
<input type="checkbox" value="烫头" v-model="hobbys">
</label>
<label>
玩摇滚
<input type="checkbox" value="玩摇滚" v-model="hobbys">
</label>
</div>

<script>
new Vue({
el: "#app",
data: {
city: '',
hobbys: []
}
})
</script>

修饰符

.lazy

在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加 lazy 修饰符,从而转为在 change 事件_之后_进行同步:

1
2
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg">

.number

如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:

1
2
3
4
<!--
即使表单元素设置的type="number"属性但是表单的value依然是字符串
-->
<input v-model.number="age" type="number">

注意: 这通常很有用,因为即使在 type=“number” 时,HTML 输入元素的值也总会返回字符串。如果这个值无法被 parseFloat() 解析,则会返回原始的值。

.trim

如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:

1
<input v-model.trim="msg">

计算属性

概念:

在Vue开发中,经常需要对项目中的属性进行一些js运算,如果将js运算代码直接插入到Vue的模板中会显得模板代码十分臃肿,并且插值语法内部只能使用js表达式。Vue为解决复杂的数据处理提供了一个新的配置选项,computed计算属性

写在当前计算属性函数内部的所有其他属性(data属性或其他computed计算属性) 都会作为当前计算属性的 **依赖(计算属性会监听这些数据的变化)**。

如果依赖不发生改变计算属性就不会重复执行,而是返回上一次计算属性执行后的值(计算属性的缓存),否则计算属性将会重新计算并返回新的结果。

基础例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<div id="example">
<!-- message 字符串翻转代码直接插入到DOM节点中显得代码过于繁琐,并且不能复用导致代码想当的臃肿-->
<p>{{ message.split('').reverse().join('') }}</p>
<div>{{ message.split('').reverse().join('') }}</div>
</div>


<!--解决方法 message属性 字符串的反转运算封装到计算属性中-->
<div id="example">
<p>{{reverseMsg}}</p>
<div>{{reverseMsg}}</div>
</div>

<script>
new Vue({
el: '#example',
data: {
message: 'Hello world'
},
computed: {
reverseMsg() {
return this.message.split('').reverse().join('')
}
}
})
</script>

计算属性的缓存

概念:

上面的计算属性功能使用methods也可以实现,但是为什么我们要使用计算属性呢?

因为计算属性会将当前计算属性函数内调用的所有 data 和 其它计算属性 作为当前计算属性的依赖(当前计算属性会订阅其函数内部所有属性和其他计算属性变化),如果依赖发生改变计算属性会自动重新计算自身的值。如果内部任何依赖都没有发生变化计算属性将不会执行内部代码,只会返回上一次计算出的值,这个特性就被称为缓存。

下面的计算属性now如果str属性不发生改变的化,下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖(即不是vue的属性)

1
2
3
4
5
computed: {
now: function () {
return this.str + Date.now()
}
}

计算属性的setter

概念: 计算属性默认只有 **getter(取值逻辑)**,不过在需要时你也可以提供一个 setter(赋值逻辑)

语法 : 计算属性名: {get(){return},set(val){}}

例子: 计算属性 fullName 会根据依赖属性 lastName、firstName 的变化而自动更新(getter),用户修改计算属性 fullName 时会反向的修改 lastName、firstName (setter)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<div id="app">
<p>姓: <input type="text" v-model="lastName"></p>
<p>名: <input type="text" v-model="firstName"></p>
<p>全名: <input type="text" v-model="fullName"></p>
</div>

<script>
new Vue({
el: '#app',
data: {
firstName: "小明",
lastName: "李"
},
computed: {
fullName: {
get() {
return `${this.lastName} ${this.firstName}`
},
set(val) {
console.log(val)
let names = val.split(' ')
this.firstName = names[1]
this.lastName = names[0]
}
}
}
})
</script>

侦听器

概念:Vue提供了一个对当前实例属性(data、computed)进行侦听的配置选项watch,当前被侦听的属性发生改变时,对应侦听器绑定的callback函数就会被触发。

语法:watch: {侦听的属性名:callback(newValue, oldValue)}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<div id="app">
<div>{{hours}}</div>
<button @click="hours++">hours add</button>
</div>

<script>
new Vue({
el: '#app',
data: {
hours: 0
},
computed: {
day() {
return this.hours % 24
}
},
watch: {
hours: function(newValue, oldValue) {
if(newValue !== oldValue) {
if(newValue < 4) {
console.log('健康游戏时间')
}else if(newValue < 6) {
// alert(`你已经高强度网上冲浪${this.hours}`)
alert(`你已经高强度网上冲浪${newValue}小时请注意休息`)
}else {
alert(`强制下线`)
}
}
},
// 侦听器也可以侦听计算属性
day(newValue, oldValue) {

}
}
})
</script>

注意:watch中的额回调函数不能使用箭头函数,使用箭头函数会导致函数内的this不指向当前实例对象自身

过滤器

概念:Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部,由“|”符号指示:

语法:filter支持全局过滤器或者局部过滤器

  • 全局过滤器,将过滤器选项配置到Vue构造函数内部
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="app">
{{str|capitalize}} // Hello
</div>

<script>
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})

new Vue({
el: '#app',
data: {
str: 'hello'
}
})
</script>
  • 局部过滤器,将过滤器选项配置到Vue实例对象内部
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<div id="app">
<div v-for="(f,i) in friends" :key="i">
<h3>姓名: {{f.name}} </h2>
<p>年龄: {{f.age}}</p>
<p>性别: {{f.sex|getSex}}</p>
</div>
</div>

<script>
new Vue({
el: '#app',
data: {
friends: [{
name: 'Max',
sex: 0,
age: 19
},
{
name: 'Jack',
sex: 1,
age: 22
},
{
name: 'Jacose',
sex: 1,
age: 19
},
{
name: 'Tim',
sex: 1,
age: 18
},
{
name: 'Jimmy',
sex: 0,
age: 20
},
{
name: 'Tom',
sex: 0,
age: 19
},
]
},
filters: {
getSex(type) {
if (type === 0) {
return '男'
}
return '女'
}
}
})
</script>

注意: filter支持传递多个参数,直接向substr传递的参数,直接传递的参数会依次作为filter方法的第二、第三…第n个参数

1
2
3
4
5
6
7
8
9
10
11
<div id="app">{{'hello'|substr(3,4,...)}}</div>
<script>
new Vue({
el: '#app'
filters: {
substr(str,start,end) {
return str.substr(start,end)
}
}
})
</script>

注意: Vue支持多个filter过滤器串联使用, 串联的过滤器会拿到前一个过滤器返回的结果依次执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div id="app">
{{str|reverseStr|capitalize}}
<!-- str将会经过reverseStr过滤器处理被反转,反转后的字符串会传入capitalize过滤器处理将首字母大写,最终的结果为 Olleh-->
</div>

<script>
new Vue({
el: '#app',
data: {
str: 'hello'
},
filters: {
capitalize (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
},
reverseStr(value) {
if (!value) return ''
return value.split('').reverse().join('')
}
}
})
</script>

组件

概念: 组件系统是 Vue 的一个重要概念,Vue允许我们将代码拆分独立成一些将小型的可复用模块。这些模块就被成为组件,使用这些组件就可以构建一个大型项目。在Vue中组件分为全局组件私有组件

组件的创建

  • 全局组件

概念: 注册在Vue构造函数中的组件,在Vue实例任何地方任何组件中都可以使用

语法: Vue.component('组件名', {组件的配置对象})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div id="app">
<gec-title></gec-title>
</div>

<script>
//全局组件,使用Vue构造函数提供的component API添加在构造函数中,在Vue实例对象的任何地方都可以使用.\
Vue.component('gec-title', {
// 这里的配置选项与Vue实例对象的配置选项(除了没有el以外)基本相同
// template 模板配置选项,Vue实例配置选项中也有此选项
// 当前组件/vue实例渲染在页面中的DOM模板
template: `
<div>
<h2>{{12 + 8}}</h2>
我是组件gec-title的模板
</div>
`
})

new Vue({
el: '#app'
})
</script>
  • 私有组件

概念: 通过components配置选项注册在Vue实例对象或其他子组件内部,只能在注册的父组件内部使用。

语法: Vue配置选项和组件配置选项都支持components属性 components: {组件名: { 组件配置选项 }}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<div id="app">
<gec-title></gec-title>
<component-a></component-a>
</div>

<script>
//全局组件,使用Vue构造函数提供的component API添加在构造函数中,在Vue实例对象的任何地方都可以使用.\
Vue.component('gec-title', {
// 这里的配置选项与Vue实例对象的配置选项(除了没有el以外)基本相同
// template 模板配置选项,Vue实例配置选项中也有此选项
// 当前组件/vue实例渲染在页面中的DOM模板
template: `
<div>
<h2>{{12 + 8}}</h2>
我是组件gec-title的模板
</div>
`
})

new Vue({
el: '#app',
components: {
// 在Vue实例中注册的局部组件 'component-a',只能在Vue的实例对象中使用
'component-a': {
template: `
<div>
<h3>我是组件A</h3>
<child-a></child-a>
<gec-title/>
</div>
`,
components: {
// 在组件'component-a'中注册的局部组件,该组件只能在组件'component-a'中使用
'child-a': {
template: '<h2>我是组件A的子组件childA</h2>'
}

}
}

}
})
</script>

注意: template属性指定DOM模板结构中必须有且仅有一个根DOM元素!

组件的data配置选项

概念: 组件设计初衷就是将哪些独立的可复用的代码块封装起来,因为对象是引用数据类型如果直接将组件的data属性设置为对象的话。同一个组件在复用时会导致多个组件同时读写同一个对象,严重的影响了组件可复用性和独立性。为了解决这个问题Vue明确规定组件的data不可以是个对象,而是一个返回data对象的工厂模式函数。

语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 普通函数
Vue.component('gec-title', {
template: `
<div>
<h2>{{name}}</h2>
</div>
`,
data() {
return {
name: '小明',
age: 18
}
}
})
// 箭头函数
Vue.component('gec-title', {
template: `
<div>
<h2>{{name}}</h2>
</div>
`,
data: () => ({
name: '小明',
age: 18
})
})

单项数据流

概念: 在Vue中组件之间是单项数据流的。单项数据流规定子组件不可以直接访问父组件的数据,只能通过props属性让父组件把数据传递给子组件。并且子组件不可以直接修改父组件传递给子组件的数据

props的使用

概念: 组件可以通过特殊的配置选项props给自身设置自定义属性,父组件就可通过props属性传值将父组件的数据传递给子组件,因为Vue是单项数据流子组件不可以修改props(props是只读的)

注意:组件的data属性与props属性不能同名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<div id="app">
<h2>{{age}}</h2>
<my-label v-bind:a="age" b="hello" :c="age >= 18?'已成年':'未成年'"/>
</div>

<script>
new Vue({
el: '#app',
data: {
age: 15
},
components: {
'my-label': {
// 给当前my-label设置了三个自定义属性a b c
// 组件组件自身就可以通过 this.props.a
props: ['a','b','c'],
data: () => ({
name: 'my-label'
}),
template: `
<div>
<p>a:{{a}}</p>
<p>b:{{b}}</p>
<p>c:{{c}}</p>
<child-a v-bind:name="name" :rootage="a"></child-a>
</div>
`,
components: {
'child-a': {
props: ['name','rootage'],
template: '<div>我是childA 父组件的name为{{name}} rootAge{{rootage}}</div>'
}
}
}
}
})
</script>

反向传值

因为Vue是单项数据流的,规定只允许父组件向子组件传递状态,而不允许子组件直接修改父组件传递过来的状态。Vue提供了自定义事件API,通过父组件监听子组件自定义事件, 当子组件想要修改父组件的状态时会通过 $emit 方法触发自定义事件。父组件对应监听子组件自定义事件的回调函数就会被触发,这样父组件自身的方法就会相应的去修改自身的状态。子组件的props就会更新

语法: $emit('自定义事件名', 向父组件回调函数传递参数)

注意: $emit只接受两个参数,参数一触发父组件监听的指定事件名,参数二(可选)向父组件监听事件回调函数传递的数据

案例: $emit的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<div id="father">
<child :zfb="money" @call="callHandel"/>
</div>

<script>
new Vue({
el:'#father',
data: {
money: 1500
},
methods: {
callHandel() {
console.log('儿子给我打电话了,说钱不够花')
this.money += 500
}
},
components: {
'child': {
props: ['zfb'],
template: `
<div>
父亲通过支付宝转生活费额度:{{zfb}}
<button @click="$emit('call')">向父亲打电话</button>
</div>
`
},

}
})

</script>

「课堂练习」

count-control子组件实现反向修改父组件的数据

  1. Vue实例中包含一个属性值为Number类型的count属性
  2. 并创建Vue实例私有子组件count-control1 与全局组件count-control2
  3. 实现子组件让父组件count data 加 1
  4. 实现子组件让父组件的count data 减 1
  5. 实现子组件让父组件的count data 加指定 number值

反向传值.gif


Vue脚手架安装使用

  • Step1 在系统变量中安装Vue脚手架工具vue-cli
1
2
#全局安装vue-cli环境配置
npm install -g @vue/cli

全局安装vuecli.gif

  • Step2访问到指定目录,使用vue指令创建新项目
1
2
#全局安装完毕后,以后 vue create 项目名称 搭建项目
vue create project_name

创建vue新项目.gif

  • Step3执行创建vue项目指令时会返回一个vue项目配置询问,先直接使用默认vue 2.0 模板
1
2
3
4
? Please pick a preset: (Use arrow keys)
> Default ([Vue 2] babel, eslint) #使用默认vue 2.0 模板
Default (Vue 3 Preview) ([Vue 3] babel, eslint) #使用默认vue 3.0 模板
Manually select features #自定义模板

配置安装vue项目.gif

  • Step4 安装完毕后
1
2
3
4
5
6
# 访问新创建的项目目录
cd project_name
#启动测试用服务开发指令
npm run serve
#项目开发完毕打包指令
npm run build

Vue-cli项目结构

1
2
3
4
5
6
7
8
9
10
webpack-demo
|- /public // 公共目录,这个目录中的文件不会被webpack打包而是作为一个静态目录
// 放置在 public 目录下或通过绝对路径被引用。这类资源将会直接被拷贝,而不会经过 webpack 的处理。
|- index.html // vue中 html模板文件
|- /src // 整个项目代码开发目录
|- main.js // 项目的入口文件
|- babel.config.js // webpack babel-loader配置文件
|- package.json // 项目的配置描述文件
|- README.md // 项目的readme文件
+|- vue.config.js // 可自定义Vue webpack相关配置的文件

局部关闭 ESlint 校检

ESlint 是一个js代码检测工具,约束开发人员的代码风格,如果想具体了解,请查阅其文档

相关文档: https://blog.csdn.net/qq_39557024/article/details/107519531
相关文档: https://blog.csdn.net/qq_39557024/article/details/107519531

public 文件夹

概念: 任何放置在 public 文件夹的静态资源都会被简单的复制,而不经过 webpack。你需要通过绝对路径来引用它们。

注意: 我们推荐将资源作为你的模块依赖图的一部分导入,这样它们会通过 webpack 的处理并获得如下好处:

  • 脚本和样式表会被压缩且打包在一起,从而避免额外的网络请求。
  • 文件丢失会直接在编译时报错,而不是到了用户端才产生 404 错误。
  • 最终生成的文件名包含了内容哈希,因此你不必担心浏览器会缓存它们的老版本。

public 目录提供的是一个应急手段,当你通过绝对路径引用它时,留意应用将会部署到哪里。如果你的应用没有部署在域名的根部,那么你需要为你的 URL 配置 publicPath 前缀,在 public/index.html 或其它通过 html-webpack-plugin 用作模板的 HTML 文件中,你需要通过 <%= BASE_URL %> 设置链接前缀:

1
2
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="stylesheet" href="<%= BASE_URL %>css/style.css">

在js文件中 使用process.env.BASE_URL作为pubulic文件的前缀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div id="app">
// 直接引入静态目录中的文件
<img alt="Vue logo" :src="`${publicPath}imgs/01.jpg`">
// 相对路径的引入会导致webpack对该文件进行打包
<img alt="Vue logo" src="../public/imgs/01.jpg">

</div>
</template>

<script>

export default {
name: 'App',
data() {
return {
// 获取公共目录路径
publicPath: process.env.BASE_URL
}
}
}
</script>

publicPath配置实在项目的根目录下vue.config.js中设置publicPath选项就好了

1
2
3
4
5
module.exports = {
publicPath: process.env.NODE_ENV === 'production'
? '/production-sub-path/' //真实开发的话,如果你的项目存放在公司域名二级路径下 只需要将 /production-sub-path/改为 /公司二级路径/就可以了
: '/'
}

webpack 相关(了解)

介绍: 在vue-cli中简单的配置webpack方式就是在Vue项目的根目录下创建vue.config.js文件。这个通过配置这个文件中 module.exports 公开的对象实现对Vue Webpack进行修改。

  1. 指定项目静态资源模块的相对路径
  2. 指定打包后文件的目录(默认dist文件)
  3. assets文件的目录
  4. 多页面应用开发
  5. css模块化 loader等
  6. 给项目添加Webpack plugin
  7. 配置当前项目的开发测试服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// vue.config.js
module.exports = {
publicPath: process.env.NODE_ENV === 'production'
? '/production-sub-path/' //真实开发的话,如果你的项目存放在公司域名二级路径下 只需要将 /production-sub-path/改为 /公司二级路径/就可以了
: '/',
devServer: { // 服务器代理,当请求了代理设置的路径时 会自动跳转到指定服务器上,解决跨域问题
proxy: {
"/search": {
target: 'http://musicapi.leanapp.cn/',
changeOrigin: true
// 当你请求 /search?123123 时 会代理到 'http://musicapi.leanapp.cn/search?123123'
}
}
}
}

了解Vue-cli src目录结构

介绍: 在src文件中main.js是整个项目的入口文件,也是实例化Vue对象的地方

因为vue-cli是模块化开发,所以整个项目不适用< script >标签引入Vue支持而是使用模块化依赖模式通过import引入Vue对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Vue from 'vue'
import App from './App.vue' // 引入单文件App组件

Vue.config.productionTip = false
// 实例化Vue
new Vue({
render: h => h(App)
// render是template字符串模板的替代方案,render是一个函数函数接收一个参数(createElement)
// 这个参数可以将组件生成为一个Vue DOM节点渲染在页面上
// 所以上面这句化等价于 template: '<App></App>'
}).$mount('#app')

// 如果 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。
// 这时可以使用 vm.$mount() 手动地挂载一个未挂载的实例。

认识单文件组件

我们观察下面的代码,在Vue实例中添加了局部组件App,如果说除了Vue实例以外其他组件可能也需要注册App局部组件的话。我们推荐将App抽离出来提供给其他组件复用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
new Vue({
el: '#app',
components: {
App: {
props: ['name','age'],
data() {
return {
address: 'bj'
}
},
template: `
<div>
<h2>用户:{{name}} 年龄:{{age}}</h2>
<p>地址:{{address}}</p>
</div>
`
}
}
})

抽离后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const App = {
props: ['name','age'],
data() {
return {
address: 'bj'
}
},
template: `
<div>
<h2>用户:{{name}} 年龄:{{age}}</h2>
<p>地址:{{address}}</p>
</div>
`
}


new Vue({
el: '#app',
components: {
App
}
})

我们在开发中推荐使用模块化开发,建议将每个可复用的组件单独封装成一个js模块,通过import引入其他文件中复用。这样的好处是便于维护更新让项目的结构更明确。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 将App组件封装成一个js模块
// App.js
export default {
props: ['name','age'],
data() {
return {
address: 'bj'
}
},
template: `
<div>
<h2>用户:{{name}} 年龄:{{age}}</h2>
<p>地址:{{address}}</p>
</div>
`
}

// 其他组件或实例注册App组件
import App from './App'

new Vue({
el: '#app',
components: {
App
}
})

Vue为了简化template字符串模板的开发(使用字符串写HTML语法js无法格式化代码,编译器无法对模板进行补全和检查),Vue提供了一个.vue文件简化了js文件创建单文件组件时template字符串模板开发不便。.vue文件单独的将template选项抽离出来以HTML的形式进行开发。

上面的App.js就可以转化为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// App.vue
// template 被抽离出来变成了一个独立的标签,内部本来使用字符串模板代码变成HTML语法
<template>
<div>
<h2>用户:{{name}} 年龄:{{age}}</h2>
<p>地址:{{address}}</p>
</div>
</template>

<script>
export default {
props: ["name", "age"],
data() {
return {
address: "bj",
}
}
}
</script>

注意

  1. .vue文件不仅支持template标签指定组件的模板样式和script 公开当前模板配置选项,还支持style标签内置的设置当前组件的样式,而且style标签支持使用sass\less\stylus预编译语言
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div class="demo">
我是Demo,在vue文件中
<!-- template内部遵循XML语法规则,所有单一型标签后面一定要跟一个/,并且template标签内部有且仅有一个根元素 -->
<input/>
</div>
</template>

<script>
export default {};
</script>
<!--
style有两个可选属性
lang="scss" 内部使用sass语法,项目要额外安装 sass-loader
lang="less" 内部使用less语法,项目要额外安装 less-loader
lang="stylus" 内部使用less语法,项目要额外安装 stylus-loader
不设置该属性则使用css
scoped 样式私有化,如果设置了该属性,style标签内的所有样式只会对组件自身有效
-->
<style lang="less" scoped>
.demo {
color: green;
}
</style>
  1. 建议使用.vue创建组件时,组件名与文件名一致并且使用每个单词首字母大写的命名方法。例: HelloWorld.vue、DemoComponent.vue

Props验证

概念:在开发中我们可以为组件的 prop 指定验证要求,例如你知道的这些类型。如果有一个需求没有被满足,则 Vue 会在浏览器控制台中警告你。这一模式会在开发中帮开发人员捕获大量的异常。

语法: props可以是一个数组,数组中的每一项都是当前组件的props属性名。props属性还是是一个对象为每个指定的props属性指定其验证规则以及默认值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
props: {
// 当前组件的propsA属性必须是Number数据类型或者为空(可忽略)
propA: Number,
// propB与propA等价
// prop为指定数据类型只需要设置该属性type值为对应数据类型的构造函数
propB: {
type: Number
},
// prop属性可以设置为必要属性,渲染该组件时必须给该属性传值并且不可为空
propC: {
type: Number,
required: true
},
// prop属性支持默认值,当没有给该prop传递属性是,该属性则会使用默认值
// 注意: 默认值优先级小于required
propD: {
type:Number,
default: 15
},
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
}
}

Props验证规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
props: {
// 通过构造函数指定prop数据类型
propA: Number,
propB: Boolean,
propC: String,
propD: Array,
propE: Object,
propF: Symbol,
propG: RegExp,
propH: Date,
propI: Cat, // 可以指定任何构造函数作为prop的type,当前prop接收到的值必须是当前指定构造函数的实例对象
propJ: Function
// prop验证支持 多个可能的类型
propK: [String, Number],
// 自定义验证规则,不满足验证条件是return false
propL: {
validator(val) {// val 传入到 propL属性的值
if (typeof val === "string") {
if (/fuck/gi.test(val)) {
console.error("组件ComponentA中 属性 PropL包含敏感词汇!");
} else {
return true;
}
} else {
console.error("数据必须是字符串");
}
return false;
}
}
}

动态组件

概念: VUe 提供了一个标签component,该标签可以使用 is attribute 来切换不同的组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<div id="app">
<component :is="`my-${name}`"></component>

<button @click="name ='a'">a</button>
<button @click="name ='b'">b</button>
</div>

<script>
new Vue({
el: '#app',
data: {
name: 'a'
},
components: {
'my-a': {
template: '<h2>我的组件A</h2>'
},
'my-b': {
template: '<h2 @click="num++">组件B{{num}}</h2>',
data() {
return {num: 7}
}
},
}
})

</script>

在动态组件上使用 keep-alive

概念: 当在这些组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<div id="app">
<!-- 失活的组件将会被缓存!-->
<keep-alive>
<component :is="`my-${name}`"></component>
</keep-alive>

<button @click="name ='a'">a</button>
<button @click="name ='b'">b</button>
</div>

<script>
new Vue({
el: '#app',
data: {
name: 'a'
},
components: {
'my-a': {
template: '<h2>我的组件A</h2>'
},
'my-b': {
template: '<h2 @click="num++">组件B{{num}}</h2>',
data() {
return {num: 7}
}
},
}
})

</script>

注意: 在上面的案例中如果没有使用keep-alive缓存失活的组件,那么失活的组件将被丢弃(卸载),组件切换时每次都是挂载一个全新的组件

异步组件(了解)

介绍: 在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。例如:

1
2
3
4
5
6
7
8
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回调传递组件定义
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})

如你所见,这个工厂函数会收到一个 resolve 回调,这个回调函数会在你从服务器得到组件定义的时候被调用。你也可以调用 reject(reason) 来表示加载失败。这里的 setTimeout 是为了演示用的,如何获取组件取决于你自己。一个推荐的做法是将异步组件和 webpack 的 code-splitting 功能一起配合使用:

1
2
3
4
5
6
Vue.component('async-webpack-example', function (resolve) {
// 这个特殊的 `require` 语法将会告诉 webpack
// 自动将你的构建代码切割成多个包,这些包
// 会通过 Ajax 请求加载
require(['./my-async-component'], resolve)
})

你也可以在工厂函数中返回一个 Promise,所以把 webpack 2 和 ES2015 语法加在一起,我们可以这样使用动态导入:

1
2
3
4
5
Vue.component(
'async-webpack-example',
// 这个动态导入会返回一个 `Promise` 对象。
() => import('./my-async-component')
)

当使用局部注册的时候,你也可以直接提供一个返回 Promise 的函数:

1
2
3
4
5
6
new Vue({
// ...
components: {
'my-component': () => import('./my-async-component')
}
})

如果你是一个 Browserify 用户同时喜欢使用异步组件,很不幸这个工具的作者明确表示异步加载“并不会被 Browserify 支持”,至少官方不会。Browserify 社区已经找到了一些变通方案,这些方案可能会对已存在的复杂应用有帮助。对于其它的场景,我们推荐直接使用 webpack,以拥有内置的头等异步支持。

懒加载状态 (2.3.0+ 新增)

这里的异步组件工厂函数也可以返回一个如下格式的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})

Ref

概念: Vue给组件元素提供了一个ref属性,绑定了ref属性的组件元素可以在当前vue实例对象中通过$refs访问其真实DOM节点(标签元素)或实例对象(组件)。

在原生html标签中使用ref

语法: <div ref="变量名"></div> 当前组件内部 this.$refs.变量名 访问其真实DOM节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 <div id="app">
<audio ref="myAudio" src="./药水歌.mp3" controls></audio>
<button @click="showAudio">show</button>
</div>

<script>
new Vue({
el: '#app',
methods: {
showAudio() {
console.log(this.$refs.myAudio)
}
}
})
</script>

在组件中中使用ref

语法<MyComponent ref="变量名" /> 当前组件内部 this.$refs.变量名 访问这个子组件的实例对象,可以获取这个组件实例的属性和方法(尽量避免使用该模式,因为他会增加组件间的耦合性)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<Demo ref="myDemo" />
</template>

<script>
import Demo from './components/Demo'

export default {
name: 'App',

methods: {
show() {
// 子组件Demo的方法在父组件中被调用了
this.$refs.myDemo.sayHello()
}
},
components: {
Demo
}
}
</script>

注意:

  1. vue实例中每个标签或组件都可以设置ref属性,绑定了ref属性的元素可以有任意个
1
2
3
4
5
6
<div id="app">
<audio ref="myAudio" src="./药水歌.mp3" controls></audio>
<button ref="myBtn" @click="showAudio">show</button>
<p ref="textEl">text</p>
</div>
// 这时可以在组件中通过 this.$refs不同的变量名访问对应的DOM元素
  1. ref配合列表渲染(v-for)使用时,$refs返回值为包含列表渲染出来的所有元素的一个数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<div id="app">
// 与v-for同级的ref 返回值都是列表渲染出来当前元素数组集合
<p v-for="num in arr" :key="num" ref="numList">
//v-for 内部的ref 返回值也是列表渲染出来当前元素数组集合
<span ref="spanList">{{num}}-span</span>
<a>没用的a</a>
{{num}}
</p>
<button @click="showRefList">click</button>
</div>

<script>
new Vue({
el: '#app',
data: {
arr: [1, 2, 3, 4, 5]
},
methods: {
showRefList() {

console.log(this.$refs.numList,
this.$refs.spanList)
// [p,p,p,p,p] , [span,span,span,span,span]
}
}
})
</script>