<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<div id="app">
{
{message}}
{
{message}}
div>
body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
<script>
var vm = new Vue({
el: "#app",
data: {
message: "Hello, Vue!"
}
});
script>
html>
MVVM(Model - View - ViewModel)是一种软件架构的设计模式,是一种简化用户界面的事件驱动编程方式。
MVVM 源自经典的 MVC(Model - View - Controller)模式。
MVVM 的核心是 ViewModel层,负责转换 Model 中的数据对象来让数据变得更容易管理和使用,其作用如下:
MVVM 模式和 MVC 模式一样,主要目的是分离视图(View)和模型(Model)
低耦合
视图(View)可以独立于 Model 和变化和修改,一个 ViewModel 可以绑定到不同的 View 上,当 View 变化的时候 Model 可以不变,当 Model 变化的时候 View 可以不变
可复用
可以把一些视图逻辑放在一个 ViewModel 里面,让很多的 View 重复这段视图逻辑
独立开发
开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计
可测试
界面素来是比较难于测试的,而现在测试可以针对 ViewModel 来写
每个 Vue 应用都是通过用 Vue 函数创建一个新的 Vue 实例开始的:
var vm = new Vue({
// 选项
})
虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm
(ViewModel 的缩写) 这个变量名表示 Vue 实例。
当创建一个 Vue 实例时,你可以传入一个选项对象。
Vue实例 有 7 个常用选项对象(属性):
el
挂载点,用来指示 Vue 编译器从什么地方开始解析 Vue 的语法,可以说是一个占位符。
其实就是跟一个元素节点相关联,假如设置
el: "#app"
,那么vm.$el === document.querySelector("#app")
的结果为true
,绑定的节点及其子节点都可以使用绑定的 Vue 实例。
data
用来组织从 view 中抽象出来的属性,可以说将视图的数据抽象出来存放在 data 中
在视图中可以使用绑定的 Vue 实例中 data 选项对象定义的属性
methods
放置页面中的业务逻辑,js方法一般都放置在 methods 中
定义函数的地方,绑定事件时可以将在此定义的函数绑定到事件中
不要使用箭头函数,如果函数使用箭头函数会导致this指向的不是vue实例$vm。因为箭头函数不绑定
this
关键字,箭头函数中的this
,指向的是函数定义位置的上下文this
computed
定义计算属性的方法,类似于 Python 中 class 里面用 @Property
装饰器修饰的函数
template
用来设置模板,会替换页面元素,包括占位符
watch
render
创建真正的 Virtual Dom
【示例】
<div id="app">
{
{msg}}
<div>这是模板的第一种使用方法1div>
div>
<template id="bot">这是模板的第三种使用方法,不常用3template>
<script>
<div id="bot">模板的第四种创建方法4</div>
script>
<script>
var vm1 = new Vue({
data: {
msg: "data属性"
},
methods: {
Personal: function () {
console.log("methods方法存放方法")
}
},
template: `模板的第二种使用方法2`,
//template:"#bot",
render: (createElement) => {
return createElement("h1",{
"class":"类名","style":"color: red"},"这一个render属性创建的虚拟dom")
},
})
script>
methods和computed其中都可以写算法,有什么区别呢?
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="javascript/vue.min.js">script>
head> <body>
<div id="app">
<p>{
{message}}p> //直接在模板中绑定表达式
<p>{
{message.split('').reverse().join('')}}p> //运用计算属性
<p>{
{reverseMessage}}p> //运用methods方式
<p>{
{methodMessage()}}p>
div>
<script>
var vm=new Vue({
el:"#app",
data:{
message:"hello"
},
computed:{
reverseMessage:function(){
return this.message.split('').reverse().join('');
}
},
methods:{
methodMessage:function () {
return this.message.split('').reverse().join('');
}
}
})
script>
body>
html>
一个 Vue 应用由一个通过 new Vue
创建的根 Vue 实例,以及可选的嵌套的、可复用的组件树组成,所有的 Vue 组件都是 Vue 实例,并且接受相同的选项对象 (一些根实例特有的选项除外)。
下图展示了实例的生命周期。你不需要立马弄明白所有的东西,不过随着不断学习和使用,它的参考价值会越来越高。
特别地,本人不打算做大前端,只是学习 Vue 的简单使用,可能细节不一定对,不过图示结果的确都是运行得到的,我看Segmentfault上有人说src引入和vue-cli的项目运行的不一样,有虚拟DOM的问题,专门学前端的别被我误导了。
可以看到在vue一整个的生命周期中会有很多钩子函数提供给我们在vue生命周期不同的时刻进行操作,那么先列出所有的钩子函数,然后我们再一一详解:
先来一波代码,各位复制在浏览器中运行,打开 console 查看就行了:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>vue生命周期学习title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
head>
<body>
<div id="app">
<h1>{
{message}}h1>
div>
body>
<script>
var vm = new Vue({
el: '#app',
data: {
message: 'Vue的生命周期'
},
beforeCreate: function() {
console.group('------beforeCreate创建前状态------');
console.log("%c%s", "color:red", "el : " + this.$el); //undefined
console.log("%c%s", "color:red", "data : " + this.$data); //undefined
console.log("%c%s", "color:red", "message: " + this.message) //undefined
},
created: function() {
console.group('------created创建完毕状态------');
console.log("%c%s", "color:red", "el : " + this.$el); //undefined
console.log("%c%s", "color:red", "data : " + this.$data); //已被初始化
console.log("%c%s", "color:red", "message: " + this.message); //已被初始化
},
beforeMount: function() {
console.group('------beforeMount挂载前状态------');
console.log("%c%s", "color:red", "el : " + (this.$el)); //已被初始化
console.log(this.$el);
console.log("%c%s", "color:red", "data : " + this.$data); //已被初始化
console.log("%c%s", "color:red", "message: " + this.message); //已被初始化
},
mounted: function() {
console.group('------mounted 挂载结束状态------');
console.log("%c%s", "color:red","el : " + this.$el); //已被初始化
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data); //已被初始化
console.log("%c%s", "color:red","message: " + this.message); //已被初始化
},
beforeUpdate: function () {
console.group('beforeUpdate 更新前状态===============》');
console.log("%c%s", "color:red","el : " + this.$el);
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data);
console.log("%c%s", "color:red","message: " + this.message);
},
updated: function () {
console.group('updated 更新完成状态===============》');
console.log("%c%s", "color:red","el : " + this.$el);
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data);
console.log("%c%s", "color:red","message: " + this.message);
},
beforeDestroy: function () {
console.group('beforeDestroy 销毁前状态===============》');
console.log("%c%s", "color:red","el : " + this.$el);
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data);
console.log("%c%s", "color:red","message: " + this.message);
},
destroyed: function () {
console.group('destroyed 销毁完成状态===============》');
console.log("%c%s", "color:red","el : " + this.$el);
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data);
console.log("%c%s", "color:red","message: " + this.message)
}
})
script>
html>
运行后打开console可以看到打印出来内容如下:
可以看到一个 Vue 实例在创建过程中调用的几个生命周期钩子。
1. 在 beforeCreate 和 created 钩子函数之间的生命周期
在这个生命周期之间,进行初始化事件,进行数据的观测,可以看到在created的时候数据已经和data属性进行绑定(放在data中的属性当值发生改变的同时,视图也会改变)。
注意看下:此时还是没有
el
选项
2. created 钩子函数和 beforeMount 间的生命周期
在这一阶段发生的事情还是比较多的。
首先会判断对象是否有 el
选项。如果有的话就继续向下编译,如果没有 el
选项,则停止编译,也就意味着停止了生命周期,直到在该 Vue 实例上调用vm.$mount(el)
。此时注释掉代码中:
el: '#app',
然后运行可以看到 created
的时候就停止了:
如果我们在后面继续调用 vm.$mount(el)
,可以发现代码继续向下执行了
vm.$mount(el) //这个el参数就是挂在的dom接点
然后,我们往下看,template
参数选项的有无对生命周期的影响。
template
参数选项,则将其作为模板编译成render函数。template
选项,则将外部 HTML 作为模板编译。template
中的模板优先级要高于 outer HTML 的优先级。对如下代码,在HTML结构中增加了一串html,在 Vue 对象中增加了 template
选项:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue生命周期学习title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
head>
<body>
<div id="app">
<h1>{
{message + '——这是在outer HTML中的'}}h1>
div>
body>
<script>
var vm = new Vue({
el: '#app',
template: "{
{message +'——这是在template中的'}}
", //在vue配置项中修改的
data: {
message: 'Vue的生命周期'
}});
script>
html>
执行后的结果可以看到在页面中显示的是:
那么将 Vue 对象中 template
的选项注释掉后打印如下信息:
这下就可以想想为什么 el
的判断要在 template
之前了,是因为 Vue 需要通过 el
找到对应的 outer template
在 Vue 对象中还有一个 render
函数,它是以 createElement
作为参数,然后做渲染操作
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue生命周期学习title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
head>
<body>
<div id="app">
<h1>{
{message + '——这是在outer HTML中的'}}h1>
div>
body>
<script>
var vm = new Vue({
el: '#app',
template: "{
{message +'——这是在template中的'}}
", //在vue配置项中修改的
data: {
message: 'Vue的生命周期'
},
render: function (createElement) {
return createElement('h1', 'this is createElement')
}
});
script>
html>
可以看到页面中渲染的是:
所以综合排名优先级:
render 函数选项 > template 选项 > outer HTML
3. beforeMount 和 mounted 钩子函数间的生命周期
可以看到此时是给 Vue 实例对象添加 $el
成员(是绿色的部分!beforeMount
钩子函数内是没有 $el
的),并且替换掉挂载的 DOM 元素。因为在之前 console 中打印的结果可以看到 beforeMount
之前 el
上还是undefined。
beforeMount
阶段及其之前应该是虚拟 DOM,到了挂载完成,也就是mounted
阶段及其之后才有真实的 DOM
4. mounted
注意看下面截图:
在 mounted
之前 h1
中还是通过 { {message}}
进行占位的,因为此时还有挂在到页面上,还是 JavaScript 中的虚拟 DOM 形式存在的。在 mounted
之后可以看到 h1
中的内容发生了变化。
挂载之前
el
和data
是不关联的,挂载到那个模板上,看优先级:render 函数选项 > template 选项 > outer HTML
5. beforeUpdate 钩子函数和 updated 钩子函数间的生命周期
当 Vue发现 data
中的数据发生了改变,会触发对应组件的重新渲染,先后调用 beforeUpdate
和 updated
钩子函数。我们在 console 中输入:
vm.message = '触发组件更新';
发现触发了组件的更新:
beforeUpdate
是检测到data
变化但是 view 还没有重新渲染,并且可以修改data
的最后时机,updated
是 view 重新渲染后触发的
在 beforeUpdate
,可以监听到 data
的变化但是 view 层没有被重新渲染,view 层的数据没有变化。
等到 updated
的时候 view 层才被重新渲染,数据更新。
6.beforeDestroy 和 destroyed 钩子函数间的生命周期
beforeDestroy
钩子函数在实例销毁之前调用。在这一步,实例仍然完全可用。
destroyed
钩子函数在 Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。
在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。
如果你熟悉虚拟 DOM 并且偏爱 JavaScript 的原始力量,你也可以不用模板,直接写渲染 (render) 函数,使用可选的 JSX 语法。
文本
数据绑定最常见的形式就是使用 “Mustache” 语法 (双大括号) 的文本插值:
<span>Message: {
{ msg }}span>
Mustache 标签将会被替代为对应数据对象上
msg
property 的值。无论何时,绑定的数据对象上msg
property 发生了改变,插值处的内容都会更新。
通过使用 v-once
指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新。
只渲染元素和组件一次,随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。
<span v-once>This will never change: {
{msg}}span>
<div v-once>
<h1>commenth1>
<p>{
{msg}}p>
div>
<my-component v-once: comment="msg">my-component>
<ul>
<li v-for="i in list" v-once>{
{i}}li>
ul>
原始 HTML
双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用 v-html
指令:
<p>使用双花括号语法:{
{ rawHtml }}p>
<p>使用 v-html 指令:<span v-html="rawHtml">span>p>
结果显示为:
使用双花括号语法:<span style="color: red">This should be red.span> 使用 v-html 指令:This should be red.
Attribute
Mustache 语法不能作用在 HTML attribute 上,遇到这种情况应该使用 v-bind
指令:
<div v-bind:id="dynamicId">div>
对于布尔 attribute (它们只要存在就意味着值为 true
),v-bind
工作起来略有不同,在这个例子中:
<button v-bind:disabled="isButtonDisabled">Buttonbutton>
如果 isButtonDisabled
的值是 null
、undefined
或 false
,则 disabled
attribute 甚至不会被包含在渲染出来的 元素中。
JavaScript 表达式
迄今为止,在我们的模板中,我们一直都只绑定简单的 property 键值。但实际上,对于所有的数据绑定,Vue.js 都提供了完全的 JavaScript 表达式支持。
{
{ number + 1 }}
{
{ ok ? 'YES' : 'NO' }}
{
{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id">div>
这些表达式会在所属 Vue 实例的数据作用域下作为 JavaScript 被解析。有个限制就是,每个绑定都只能包含单个表达式,所以下面的例子都不会生效。
{
{ var a = 1 }}
{
{ if (ok) { return message } }}
指令 (Directives) 是带有 v-
前缀的特殊 attribute。指令 attribute 的值预期是单个 JavaScript 表达式 。
指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
<p v-if="seen">现在你看到我了p>
这里,v-if
指令将根据表达式 seen
的值的真假来插入/移除 元素。
参数
一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如,v-bind
指令可以用于响应式地更新 HTML attribute:
<a v-bind:href="url">...a>
在这里 href
是参数,告知 v-bind
指令将该元素的 href
attribute 与表达式 url
的值绑定。
另一个例子是 v-on
指令,它用于监听 DOM 事件:
<a v-on:click="doSomething">...a>
在这里参数是监听的事件名。
动态参数
从 2.6.0 开始,可以用方括号括起来的 JavaScript 表达式作为一个指令的参数:
<a v-bind:[attributeName]="url"> ... a>
这里的 attributeName
会被作为一个 JavaScript 表达式进行动态求值,求得的值将会作为最终的参数来使用。例如,如果你的 Vue 实例有一个 data
property attributeName
,其值为 "href"
,那么这个绑定将等价于 v-bind:href
。
同样地,你可以使用动态参数为一个动态的事件名绑定处理函数:
<a v-on:[eventName]="doSomething"> ... a>
在这个示例中,当 eventName
的值为 "focus"
时,v-on:[eventName]
将等价于 v-on:focus
。
动态参数预期会求出一个字符串,异常情况下值为
null
。这个特殊的null
值可以被显性地用于移除绑定。
修饰符
饰符 (modifier) 是以半角句号 .
指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent
修饰符告诉 v-on
指令对于触发的事件调用 event.preventDefault()
:
<form v-on:submit.prevent="onSubmit">...form>
缩写
v-
前缀作为一种视觉提示,用来识别模板中 Vue 特定的 attribute。当你在使用 Vue.js 为现有标签添加动态行为 (dynamic behavior) 时,v-
前缀很有帮助,然而,对于一些频繁用到的指令来说,就会感到使用繁琐。同时,在构建由 Vue 管理所有模板的[单页面应用程序 (SPA - single page application) 时,v-
前缀也变得没那么重要了。因此,Vue 为 v-bind
和 v-on
这两个最常用的指令,提供了特定简写:
v-bind
缩写:
<a v-bind:href="url">...a>
<a :href="url">...a>
<a :[key]="url"> ... a>
v-on
缩写:
<a v-on:click="doSomething">...a>
<a @click="doSomething">...a>
<a @[event]="doSomething"> ... a>
它们看起来可能与普通的 HTML 略有不同,但 :
与 @
对于 attribute 名来说都是合法字符,在所有支持 Vue 的浏览器都能被正确地解析。而且,它们不会出现在最终渲染的标记中。缩写语法是完全可选的,但随着你更深入地了解它们的作用,你会庆幸拥有它们。
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如:
<div id="example">
{
{ message.split('').reverse().join('') }}
div>
在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 message
的翻转字符串。当你想要在模板中的多处包含此翻转字符串时,就会更加难以处理。
所以,对于任何复杂逻辑,你都应当使用计算属性。
<div id="example">
<p>Original message: "{
{ message }}"p>
<p>Computed reversed message: "{
{ reversedMessage }}"p>
div>
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
});
结果:
Original message: "Hello"
Computed reversed message: "olleH"
这里我们声明了一个计算属性 reversedMessage
。我们提供的函数将用作 property vm.reversedMessage
的 getter 函数:
console.log(vm.reversedMessage) // => 'olleH'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'
你可以打开浏览器的控制台,自行修改例子中的 vm。vm.reversedMessage
的值始终取决于 vm.message
的值。
你可以像绑定普通 property 一样在模板中绑定计算属性。Vue 知道 vm.reversedMessage
依赖于 vm.message
,因此当 vm.message
发生改变时,所有依赖 vm.reversedMessage
的绑定也会更新。而且最妙的是我们已经以声明的方式创建了这种依赖关系:计算属性的 getter 函数是没有副作用 (side effect) 的,这使它更易于测试和理解。
我们可以通过在表达式中调用方法来达到同样的效果:
<p>Reversed message: "{
{ reversedMessage() }}"p>
// 在组件中
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message
还没有发生改变,多次访问 reversedMessage
计算属性会立即返回之前的计算结果,而不必再次执行函数。
这也同样意味着下面的计算属性将不再更新,因为 Date.now()
不是响应式依赖:
computed: {
now: function () {
return Date.now()
}
}
相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。
我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。
Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch
——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch
回调。细想一下这个例子:
<div id="demo">{
{ fullName }}div>
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
上面代码是命令式且重复的。将它与计算属性的版本进行比较:
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
好得多了,不是吗?
计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:
// ...
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
// ...
现在再运行 vm.fullName = 'John Doe'
时,setter 会被调用,vm.firstName
和 vm.lastName
也会相应地被更新。
操作元素的 class 列表和内联样式是数据绑定的一个常见需求。因为它们都是 attribute,所以我们可以用 v-bind
处理它们:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错。因此,在将 v-bind
用于 class
和 style
时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组。
1. 对象语法
我们可以传给 v-bind:class
一个对象,以动态地切换 class:
<div v-bind:class="{ active: isActive }">div>
上面的语法表示 active
这个 class 存在与否将取决于数据 property isActive
的 truthiness。
你可以在对象中传入更多字段来动态切换多个 class。此外,v-bind:class
指令也可以与普通的 class attribute 共存。当有如下模板:
<div class="static" v-bind:class="{ active: isActive, 'text-danger': hasError }">div>
和如下 data:
data: {
isActive: true,
hasError: false
}
结果渲染为:
<div class="static active">div>
当 isActive
或者 hasError
变化时,class 列表将相应地更新。例如,如果 hasError
的值为 true
,class 列表将变为 "static active text-danger"
。
绑定的数据对象不必内联定义在模板里:
<div v-bind:class="classObject">div>
data: {
classObject: {
active: true,
'text-danger': false
}
}
渲染的结果和上面一样。我们也可以在这里绑定一个返回对象的计算属性。这是一个常用且强大的模式:
<div v-bind:class="classObject">div>
data: {
isActive: true,
error: null
},
computed: {
classObject: function () {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}
2. 数组语法
我们可以把一个数组传给 v-bind:class
,以应用一个 class 列表:
<div v-bind:class="[activeClass, errorClass]">div>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
渲染为:
<div class="active text-danger">div>
如果你也想根据条件切换列表中的 class,可以用三元表达式:
<div v-bind:class="[isActive ? activeClass : '', errorClass]">div>
这样写将始终添加 errorClass
,但是只有在 isActive
是 truthy时才添加 activeClass
。
不过,当有多个条件 class 时这样写有些繁琐。所以在数组语法中也可以使用对象语法:
<div v-bind:class="[{ active: isActive }, errorClass]">div>
3. 用在组件上
这个章节假设你已经对 Vue 组件有一定的了解。当然你也可以先跳过这里,稍后再回过头来看。
当在一个自定义组件上使用 class
property 时,这些 class 将被添加到该组件的根元素上面。这个元素上已经存在的 class 不会被覆盖。
例如,如果你声明了这个组件:
Vue.component('my-component', {
template: '
'})
然后在使用它的时候添加一些 class:
<my-component class="baz boo">my-component>
HTML 将被渲染为:
<p class="foo bar baz boo">Hip>
对于带数据绑定 class 也同样适用:
<my-component v-bind:class="{ active: isActive }">my-component>
当 isActive
为 truthy[1] 时,HTML 将被渲染成为:
<p class="foo bar active">Hip>
1. 对象语法
v-bind:style
的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名:
<div v-bind:style="{
color: activeColor, fontSize: fontSize + 'px' }">div>
data: {
activeColor: 'red',
fontSize: 30
}
直接绑定到一个样式对象通常更好,这会让模板更清晰:
<div v-bind:style="styleObject">div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
同样的,对象语法常常结合返回对象的计算属性使用。
2. 数组语法
v-bind:style
的数组语法可以将多个样式对象应用到同一个元素上:
<div v-bind:style="[baseStyles, overridingStyles]">div>
3. 自动添加前缀
当 v-bind:style
使用需要添加浏览器引擎前缀的 CSS property 时,如 transform
,Vue.js 会自动侦测并添加相应的前缀。
v-if
指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。
<h1 v-if="awesome">Vue is awesome!h1>
也可以用 v-else
添加一个“else 块”:
<h1 v-if="awesome">Vue is awesome!h1>
<h1 v-else>Oh no h1>
1. 在 元素上使用 v-if 条件渲染分组
因为 v-if
是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢?此时可以把一个 元素当做不可见的包裹元素,并在上面使用
v-if
。最终的渲染结果将不包含 元素。
<template v-if="ok">
<h1>Titleh1>
<p>Paragraph 1p>
<p>Paragraph 2p>
template>
2. v-else
可以使用 v-else
指令来表示 v-if
的“else 块”:
<div v-if="Math.random() > 0.5">
Now you see me
div>
<div v-else>
Now you don't
div>
v-else
元素必须紧跟在带 v-if
或者 v-else-if
的元素的后面,否则它将不会被识别。
3. v-else-if
v-else-if
,顾名思义,充当 v-if
的“else-if 块”,可以连续使用:
<div v-if="type === 'A'">
A
div>
<div v-else-if="type === 'B'">
B
div>
<div v-else-if="type === 'C'">
C
div>
<div v-else>
Not A/B/C
div>
类似于 v-else
,v-else-if
也必须紧跟在带 v-if
或者 v-else-if
的元素之后。
4. 用 key 管理可复用的元素
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做除了使 Vue 变得非常快之外,还有其它一些好处。例如,如果你允许用户在不同的登录方式之间切换:
<template v-if="loginType === 'username'">
<label>Usernamelabel>
<input placeholder="Enter your username">
template>
<template v-else>
<label>Emaillabel>
<input placeholder="Enter your email address">
template>
那么在上面的代码中切换 loginType
将不会清除用户已经输入的内容。因为两个模板使用了相同的元素, 不会被替换掉——仅仅是替换了它的
placeholder
。
这样也不总是符合实际需求,所以 Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key
attribute 即可:
<template v-if="loginType === 'username'">
<label>Usernamelabel>
<input placeholder="Enter your username" key="username-input">
template>
<template v-else>
<label>Emaillabel>
<input placeholder="Enter your email address" key="email-input">
template>
注意,
元素仍然会被高效地复用,因为它们没有添加
key
attribute。
另一个用于根据条件展示元素的选项是 v-show
指令。用法大致一样:
<h1 v-show="ok">Hello!h1>
不同的是带有 v-show
的元素始终会被渲染并保留在 DOM 中。v-show
只是简单地切换元素的 CSS property display
。
注意,
v-show
不支持元素,也不支持
v-else
。
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show
较好;如果在运行时条件很少改变,则使用 v-if
较好。
当 v-if
与 v-for
一起使用时,v-for
具有比 v-if
更高的优先级。
不推荐同时使用
v-if
和v-for
。
我们可以用 v-for
指令基于一个数组来渲染一个列表。v-for
指令需要使用 item in items
形式的特殊语法,其中 items
是源数据数组,而 item
则是被迭代的数组元素的别名。
<ul id="example-1">
<li v-for="item in items" :key="item.message">
{
{ item.message }}
li>
ul>
var example1 = new Vue({
el: '#example-1',
data: {
items: [
{
message: 'Foo' },
{
message: 'Bar' }
]
}
})
结果:
- Foo
- Bar
在 v-for
块中,我们可以访问所有父作用域的 property。v-for
还支持一个可选的第二个参数,即当前项的索引。
<ul id="example-2">
<li v-for="(item, index) in items">
{
{ parentMessage }} - {
{ index }} - {
{ item.message }}
li>
ul>
var example2 = new Vue({
el: '#example-2',
data: {
parentMessage: 'Parent',
items: [
{
message: 'Foo' },
{
message: 'Bar' }
]
}
})
结果:
- Parent - 0 - Foo
- Parent - 1 - Bar
你也可以用 of
替代 in
作为分隔符,因为它更接近 JavaScript 迭代器的语法:
<div v-for="item of items">div>
你也可以用 v-for
来遍历一个对象的 property。
<ul id="v-for-object" class="demo">
<li v-for="value in object">
{
{ value }}
li>
ul>
new Vue({
el: '#v-for-object',
data: {
object: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
}
})
结果:
- How to do lists in Vue
- Jane Doe
- 2016-04-10
你也可以提供第二个的参数为 property 名称 (也就是键名):
<div v-for="(value, name) in object">
{
{ name }}: {
{ value }}
div>
title: How to do lists in Vue
author: Jane Doe
publishedAt: 2016-04-10
还可以用第三个参数作为索引:
<div v-for="(value, name, index) in object">
{
{ index }}. {
{ name }}: {
{ value }}
div>
title: How to do lists in Vue
author: Jane Doe
publishedAt: 2016-04-10
在遍历对象时,会按
Object.keys()
的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下都一致。
当 Vue 正在更新使用 v-for
渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。
这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key
attribute:
<div v-for="item in items" v-bind:key="item.id">
div>
建议尽可能在使用 v-for
时提供 key
attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。
因为它是 Vue 识别节点的一个通用机制,
key
并不仅与v-for
特别关联。
不要使用对象或数组之类的非基本类型值作为
v-for
的key
。请用字符串或数值类型的值。
1. 变更方法
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
你可以打开控制台,然后对前面例子的 items
数组尝试调用变更方法。比如 example1.items.push({ message: 'Baz' })
。
2. 替换数组
变更方法,顾名思义,会变更调用了这些方法的原始数组。相比之下,也有非变更方法,例如 filter()
、concat()
和 slice()
。它们不会变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组:
example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})
你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。
3. 显示过滤/排序后的结果
有时,我们想要显示一个数组经过过滤或排序后的版本,而不实际变更或重置原始数据。在这种情况下,可以创建一个计算属性,来返回过滤或排序后的数组。
例如:
<li v-for="n in evenNumbers">{
{ n }}li>
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
evenNumbers: function () {
return this.numbers.filter(function (number) {
return number % 2 === 0
})
}
}
在计算属性不适用的情况下 (例如,在嵌套 v-for
循环中) 你可以使用一个方法:
<ul v-for="set in sets">
<li v-for="n in even(set)">{
{ n }}li>
ul>
data: {
sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
},
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}
}
4. 在 v-for 里使用值范围
v-for
也可以接受整数。在这种情况下,它会把模板重复对应次数。
<div>
<span v-for="n in 10">{
{ n }} span>
div>
结果:
1 2 3 4 5 6 7 8 9 10
5. 在 上使用 v-for
类似于 v-if
,你也可以利用带有 v-for
的 来循环渲染一段包含多个元素的内容。比如:
<ul>
<template v-for="item in items">
<li>{
{ item.msg }}li>
<li class="divider" role="presentation">li>
template>
ul>
6. v-for 与 v-if 一同使用
当它们处于同一节点,v-for
的优先级比 v-if
更高,这意味着 v-if
将分别重复运行于每个 v-for
循环中。当你只想为部分项渲染节点时,这种优先级的机制会十分有用,如下:
<li v-for="todo in todos" v-if="!todo.isComplete">
{
{ todo }}
li>
上面的代码将只渲染未完成的 todo。
而如果你的目的是有条件地跳过循环的执行,那么可以将 v-if
置于外层元素 (或 ) 上。如:
<ul v-if="todos.length">
<li v-for="todo in todos">
{
{ todo }}
li>
ul>
<p v-else>No todos left!p>
7. 在组件上使用 v-for
在自定义组件上,你可以像在任何普通元素上一样使用 v-for
。
<my-component v-for="item in items" :key="item.id">my-component>
然而,任何数据都不会被自动传递到组件里,因为组件有自己独立的作用域。为了把迭代数据传递到组件里,我们要使用 prop:
<my-component v-for="(item, index) in items" v-bind:item="item" v-bind:index="index" v-bind:key="item.id" >my-component>
不自动将 item
注入到组件里的原因是,这会使得组件与 v-for
的运作紧密耦合。明确组件数据的来源能够使组件在其他场合重复使用。
下面是一个简单的 todo 列表的完整例子:
<div id="todo-list-example">
<form v-on:submit.prevent="addNewTodo">
<label for="new-todo">Add a todolabel>
<input v-model="newTodoText" id="new-todo" placeholder="E.g. Feed the cat" />
<button>Addbutton>
form>
<ul>
<li is="todo-item" v-for="(todo, index) in todos" v-bind:key="todo.id"
v-bind:title="todo.title" v-on:remove="todos.splice(index, 1)" >li>
ul>
div>
注意这里的
is="todo-item"
attribute。这种做法在使用 DOM 模板时是十分必要的,因为在元素内只有
元素会被看作有效内容。这样做实现的效果与
相同,但是可以避开一些潜在的浏览器解析错误。查看 DOM 模板解析说明 来了解更多信息。
Vue.component('todo-item', {
template: '\
\
{
{ title }}\
\
\
',
props: ['title']
})
new Vue({
el: '#todo-list-example',
data: {
newTodoText: '',
todos: [
{
id: 1,
title: 'Do the dishes',
},
{
id: 2,
title: 'Take out the trash',
},
{
id: 3,
title: 'Mow the lawn'
}
],
nextTodoId: 4
},
methods: {
addNewTodo: function () {
this.todos.push({
id: this.nextTodoId++,
title: this.newTodoText
})
this.newTodoText = ''
}
}
})
可以用 v-on
指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。
示例:
<div id="example-1">
<button v-on:click="counter += 1">Add 1button>
<p>The button above has been clicked {
{ counter }} times.p>
div>
var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
}
})
然而许多事件处理逻辑会更为复杂,所以直接把 JavaScript 代码写在 v-on
指令中是不可行的。因此 v-on
还可以接收一个需要调用的方法名称。
示例:
<div id="example-2">
<!-- `greet` 是在下面定义的方法名 -->
<button v-on:click="greet">Greet</button>
</div>
var example2 = new Vue({
el: '#example-2',
data: {
name: 'Vue.js'
},
// 在 `methods` 对象中定义方法
methods: {
greet: function (event) {
// `this` 在方法里指向当前 Vue 实例
alert('Hello ' + this.name + '!')
// `event` 是原生 DOM 事件
if (event) {
alert(event.target.tagName)
}
}
}
})
// 也可以用 JavaScript 直接调用方法
example2.greet() // => 'Hello Vue.js!'
除了直接绑定到一个方法,也可以在内联 JavaScript 语句中调用方法:
<div id="example-3">
<button v-on:click="say('hi')">Say hibutton>
<button v-on:click="say('what')">Say whatbutton>
div>
new Vue({
el: '#example-3',
methods: {
say: function (message) {
alert(message)
}
}
})
有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event
把它传入方法:
<button v-on:click="warn('Form cannot be submitted yet.', $event)">
Submit
button>
// ...
methods: {
warn: function (message, event) {
// 现在我们可以访问原生事件对象
if (event) {
event.preventDefault()
}
alert(message)
}
}
在事件处理程序中调用 event.preventDefault()
或 event.stopPropagation()
是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。
为了解决这个问题,Vue.js 为 v-on
提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。
.stop
.prevent
.capture
.self
.once
.passive
<a v-on:click.stop="doThis">a>
<form v-on:submit.prevent="onSubmit">form>
<a v-on:click.stop.prevent="doThat">a>
<form v-on:submit.prevent>form>
<div v-on:click.capture="doThis">...div>
<div v-on:click.self="doThat">...div>
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用
v-on:click.prevent.self
会阻止所有的点击,而v-on:click.self.prevent
只会阻止对元素自身的点击。
<a v-on:click.once="doThis">a>
不像其它只能对原生的 DOM 事件起作用的修饰符,
.once
修饰符还能被用到自定义的组件事件上
Vue 还对应 addEventListener
中的 passive
选项提供了 .passive
修饰符。
<div v-on:scroll.passive="onScroll">...div>
这个 .passive
修饰符尤其能够提升移动端的性能。
不要把
.passive
和.prevent
一起使用,因为.prevent
将会被忽略,同时浏览器可能会向你展示一个警告。请记住,.passive
会告诉浏览器你不想阻止事件的默认行为。
在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on
在监听键盘事件时添加按键修饰符:
<input v-on:keyup.enter="submit">
你可以直接将 KeyboardEvent.key
暴露的任意有效按键名转换为 kebab-case 来作为修饰符。
<input v-on:keyup.page-down="onPageDown">
在上述示例中,处理函数只会在 $event.key
等于 PageDown
时被调用。
可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。
.ctrl
.alt
.shift
.meta
注意:在 Mac 系统键盘上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞)。在 Sun 操作系统键盘上,meta 对应实心宝石键 (◆)。在其他特定键盘上,尤其在 MIT 和 Lisp 机器的键盘、以及其后继产品,比如 Knight 键盘、space-cadet 键盘,meta 被标记为“META”。在 Symbolics 键盘上,meta 被标记为“META”或者“Meta”。
例如:
<input v-on:keyup.alt.67="clear">
<div v-on:click.ctrl="doSomething">Do somethingdiv>
.exact
修饰符允许你控制由精确的系统修饰符组合触发的事件。
<button v-on:click.ctrl="onClick">Abutton>
<button v-on:click.ctrl.exact="onCtrlClick">Abutton>
<button v-on:click.exact="onClick">Abutton>
.left
.right
.middle
这些修饰符会限制处理函数仅响应特定的鼠标按钮。
你可以用 v-model
指令在表单 、
及
元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但
v-model
本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
v-model
会忽略所有表单元素的value
、checked
、selected
attribute 的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的data
选项中声明初始值。
v-model
在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
value
property 和 input
事件;checked
property 和 change
事件;value
作为 prop 并将 change
作为事件。对于需要使用输入法 (如中文、日文、韩文等) 的语言,你会发现
v-model
不会在输入法组合文字过程中得到更新。如果你也想处理这个过程,请使用input
事件。
1. 文本
<div id="app">
<input v-model="message" placeholder="edit me">
<p>Message is: {
{ message }}p>
div>
new Vue({
el: '#app',
data: {
message: ''
}
})
2. 多行文本
<div id="app">
<span>Multiline message is:span>
<p style="white-space: pre-line;">{
{ message }}p>
<br>
<textarea v-model="message" placeholder="add multiple lines">textarea>
div>
new Vue({
el: '#app',
data: {
message: ''
}
})
在文本区域插值 (
) 并不会生效,应用
v-model
来代替。
3. 复选框
单个复选框,绑定到布尔值:
<div id="app">
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{
{ checked }}label>
div>
var vm = new Vue({
el: '#app',
data: {
checked: true
}
})
多个复选框,绑定到同一个数组:
<div id="app">
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jacklabel>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">Johnlabel>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mikelabel>
<br>
<span>Checked names: {
{ checkedNames }}span>
div>
var vm = new Vue({
el: '#app',
data: {
checkedNames: []
}
})
4. 单选按钮
<div id="app">
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">Onelabel>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Twolabel>
<br>
<span>Picked: {
{ picked }}span>
div>
var vm = new Vue({
el: '#app',
data: {
checkepickedd: ''
}
})
5. 选择框
<div id="app">
<select v-model="selected">
<option disabled value="">请选择option>
<option>Aoption>
<option>Boption>
<option>Coption>
select>
<span>Selected: {
{ selected }}span>
div>
var vm = new Vue({
el: '#app',
data: {
selected: ''
}
})
如果
v-model
表达式的初始值未能匹配任何选项,元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐像上面这样提供一个值为空的禁用选项
多选时 (绑定到一个数组):
<div id="app">
<select v-model="selected" multiple style="width: 50px;">
<option>Aoption>
<option>Boption>
<option>Coption>
select>
<br>
<span>Selected: {
{ selected }}span>
div>
var vm = new Vue({
el: '#app',
data: {
selected: []
}
})
用 v-for
渲染的动态选项:
<div id="app">
<select v-model="selected">
<option v-for="option in options" v-bind:value="option.value">
{
{ option.text }}
option>
select>
<span>Selected: {
{ selected }}span>
div>
var vm = new Vue({
el: '#app',
data: {
selected: 'A',
options: [{
text: 'One',
value: 'A'
},
{
text: 'Two',
value: 'B'
},
{
text: 'Three',
value: 'C'
}
]
}
})
对于单选按钮,复选框及选择框的选项,v-model
绑定的值通常是静态字符串 (对于复选框也可以是布尔值):
<input type="radio" v-model="picked" value="a">
<input type="checkbox" v-model="toggle">
<select v-model="selected">
<option value="abc">ABCoption>
select>
但是有时我们可能想把值绑定到 Vue 实例的一个动态 property 上,这时可以用 v-bind
实现,并且这个 property 的值可以不是字符串。
1. 复选框
<input type="checkbox" v-model="toggle" true-value="yes" false-value="no">
// 当选中时
vm.toggle === 'yes'
// 当没有选中时
vm.toggle === 'no'
这里的
true-value
和false-value
attribute 并不会影响输入控件的value
attribute,因为浏览器在提交表单时并不会包含未被选中的复选框。如果要确保表单中这两个值中的一个能够被提交,(即“yes”或“no”),请换用单选按钮。
2. 单选按钮
<input type="radio" v-model="pick" v-bind:value="a">
// 当选中时
vm.pick === vm.a
3. 选择框的选项
<div id="app">
<select v-model="selected">
<option v-bind:value="{ number: 123 }">123option>
select>
div>
// 当选中时
typeof vm.selected // => 'object'
vm.selected.number // => 123
.lazy
在默认情况下,v-model
在每次 input
事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加 lazy
修饰符,从而转为在 change
事件_之后_进行同步:
<input v-model.lazy="msg">
.number
如果想自动将用户的输入值转为数值类型,可以给 v-model
添加 number
修饰符:
<input v-model.number="age" type="number">
这通常很有用,因为即使在 type="number"
时,HTML 输入元素的值也总会返回字符串。如果这个值无法被 parseFloat()
解析,则会返回原始的值。
.trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model
添加 trim
修饰符:
<input v-model.trim="msg">