四、具体实现
2、模板语法
2.1、插值
- 文本 数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值 :{{message}
通过使用 v-once 指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新。但请留心这会影响到该节点上的其它数据绑定 - 原始HTML
双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用v-html
指令:
这个span
的内容将会被替换成为属性值rawHtml
,直接作为 HTML——会忽略解析属性值中的数据绑定。注意,你不能使用v-html
来复合局部模板,因为 Vue 不是基于字符串的模板引擎。反之,对于用户界面 (UI),组件更适合作为可重用和可组合的基本单位。 - 特性
Mustache 语法不能作用在 HTML 特性上,遇到这种情况应该使用 v-bind 指令: - 使用javascript表达式
对于所有的数据绑定,Vue.js 都提供了完全的 JavaScript 表达式支持。
这些表达式会在所属 Vue 实例的数据作用域下作为 JavaScript 被解析。有个限制就是,每个绑定都只能包含单个表达式,所以下面的例子都不会生效。
2.2、指令
指令 (Directives) 是带有 v-
前缀的特殊特性。指令特性的值预期是单个 JavaScript 表达式 (v-for
是例外情况,稍后我们再讨论)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
<p v-if="seen">现在你看到我了</p> 这里,v-if 指令将根据表达式 seen 的值的真假来插入/移除 <p> 元素。
- 参数
一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如,v-bind 指令可以用于响应式地更新 HTML 特性: <a v-bind:href="url">...</a> 在这里 href 是参数,告知 v-bind 指令将该元素的 href 特性与表达式 url 的值绑定。 另一个例子是 v-on 指令,它用于监听 DOM 事件: <a v-on:click="doSomething">...</a>
- 动态参数 2.6新增
<a v-bind:[attributeName]="url"> ... </a> <a v-on:[eventName]="doSomething"> ... </a>
对动态参数的值的约束
动态参数预期会求出一个字符串,异常情况下值为
null
。这个特殊的null
值可以被显性地用于移除绑定。任何其它非字符串类型的值都将会触发一个警告。对动态参数表达式的约束
动态参数表达式有一些语法约束,因为某些字符,例如空格和引号,放在 HTML 特性名里是无效的。同样,在 DOM 中使用模板时你需要回避大写键名。
另外,如果你在 DOM 中使用模板 (直接在一个 HTML 文件里撰写模板),需要留意浏览器会把特性名全部强制转为小写 - 修饰符
修饰符 (modifier) 是以半角句号.
指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent
修饰符告诉v-on
指令对于触发的事件调用event.preventDefault():
<form v-on:submit.prevent="onSubmit">...</form>
2.3、缩写
v-
前缀作为一种视觉提示,用来识别模板中 Vue 特定的特性。当你在使用 Vue.js 为现有标签添加动态行为 (dynamic behavior) 时,v-
前缀很有帮助,然而,对于一些频繁用到的指令来说,就会感到使用繁琐。同时,在构建由 Vue 管理所有模板的单页面应用程序 (SPA - single page application)时,v-
前缀也变得没那么重要了。因此,Vue 为 v-bind
和 v-on
这两个最常用的指令,提供了特定简写:
<!-- 完整语法 --> <a v-bind:href="url">...</a> <!-- 缩写 --> <a :href="url">...</a> <!-- 完整语法 --> <a v-on:click="doSomething">...</a> <!-- 缩写 --> <a @click="doSomething">...</a>
它们看起来可能与普通的 HTML 略有不同,但 :
与 @
对于特性名来说都是合法字符,在所有支持 Vue 的浏览器都能被正确地解析。而且,它们不会出现在最终渲染的标记中。缩写语法是完全可选的,但随着你更深入地了解它们的作用,你会庆幸拥有它们。
3、计算属性和侦听器
3.1、计算属性
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。
所以,对于任何复杂逻辑,你都应当使用计算属性。
- 计算属性缓存VS方法
我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。如果你不希望有缓存,请用方法来替代。 - 计算属性vs侦听属性
通常更好的做法是使用计算属性而不是命令式的watch
- 计算属性的setter方法
3.2、侦听器
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch
选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
使用 watch
选项允许我们执行异步操作 (访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
4、Class与Style绑定
操作元素的 class 列表和内联样式是数据绑定的一个常见需求。因为它们都是属性,所以我们可以用 v-bind
处理它们:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错。因此,在将 v-bind
用于 class
和 style
时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组。
4.1、绑定HTML Class
- 对象语法
我们可以传给v-bind:class
一个对象,以动态地切换 class:
<div v-bind:class="{ active: isActive }"></div>
可以在对象中传入更多属性来动态切换多个 class。此外,v-bind:class
指令也可以与普通的 class 属性共存。 - 数组语法
我们可以把一个数组传给 v-bind:class
,以应用一个 class 列表
<div v-bind:class="[activeClass, errorClass]"></div> data: { activeClass: 'active', errorClass: 'text-danger' } <!--渲染为--> <div class="active text-danger"></div>
3.用在组件上
4.2、绑定内联样式
- 对象语法
- 数组语法
- 自动添加前缀
- 多重置
5、条件渲染
5.1、v-if v-else v-else-if 用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。
5.2、用 key 管理可复用的元素 ,所以 Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key
属性即可
<template v-if="loginType === 'username'"> <label>Username</label> <input placeholder="Enter your username" key="username-input"> </template> <template v-else> <label>Email</label> <input placeholder="Enter your email address" key="email-input"> </template>
5.3、v-show 另一个用于根据条件展示元素的选项
不同的是带有 v-show
的元素始终会被渲染并保留在 DOM 中。v-show
只是简单地切换元素的 CSS 属性 display
。
v-if VS v-show
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show
较好;如果在运行时条件很少改变,则使用 v-if
较好。
6、列表渲染
6.1、用v-for把一个数组对应为一组元素
6.2、在v-for里面使用对象
6.3、维护状态
当 Vue 正在更新使用 v-for
渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。这个类似 Vue 1.x 的 track- by="$index"
。
这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key
属性
<div v-for="item in items" v-bind:key="item.id"> <!-- 内容 --> </div>
因为它是 Vue 识别节点的一个通用机制,key
并不仅与 v-for
特别关联。后面我们将在指南中看到,它还具有其它用途。
6.4、数组更新检测
-
- 变异方法(mutation method)
Vue 将被侦听的数组的变异方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
push() 推
pop() 弹
shift() 位移
unshift() 松开
splice() 拼接
sort() 排序
reverse() 反转
- 替换数
变异方法,顾名思义,会改变调用了这些方法的原始数组。相比之下,也有非变异 (non-mutating method) 方法,例如
filter()
、concat()
和slice()
。它们不会改变原始数组,而总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组:example1.items = example1.items.filter(function (item) { return item.message.match(/Foo/) })
你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。
注意事项
- 变异方法(mutation method)
由于 JavaScript 的限制,Vue 不能检测以下数组的变动:
-
-
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
- 当你利用索引直接设置一个数组项时,例如:
-
vm.items[indexOfItem] = newValue
-
- 相同的效果,同时也将在响应式系统内触发状态更新:
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
-
- 你也可以使用
-
- 实例方法,该方法是全局方法
Vue.set
-
- 的一个别名:
vm.$set(vm.items, indexOfItem, newValue)
为了解决第二类问题,你可以使用 splice
:
vm.items.splice(newLength)
6.5、对象变更检测注意事项
还是由于 JavaScript 的限制,Vue 不能检测对象属性的添加或删除:
var vm = new Vue({
data: {
a: 1
}
})
// `vm.a` 现在是响应式的
vm.b = 2
// `vm.b` 不是响应式的
对于已经创建的实例,Vue 不允许动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, propertyName, value)
方法向嵌套对象添加响应式属性。例如,对于:
var vm = new Vue({
data: {
userProfile: {
name: 'Anika'
}
}
})
你可以添加一个新的 age
属性到嵌套的 userProfile
对象:
Vue.set(vm.userProfile, 'age', 27)
你还可以使用 vm.$set
实例方法,它只是全局 Vue.set
的别名:
vm.$set(vm.userProfile, 'age', 27)
有时你可能需要为已有对象赋值多个新属性,比如使用 Object.assign()
或 _.extend()
。在这种情况下,你应该用两个对象的属性创建一个新的对象。所以,如果你想添加新的响应式属性,不要像这样:
Object.assign(vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
你应该这样做:
vm.userProfile = Object.assign({}, vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
6.6、显示过滤/排序后的结果
创建一个计算属性,来返回过滤或排序后的数组。
在计算属性不适用的情况下 (例如,在嵌套 v-for
循环中) 你可以使用一个方法:
6.7、在 v-for 里使用值范围
6.8、在 <template> 上使用 v-for
6.9、v-for 与 v-if 一同使用
6.10、在组件上使用 v-for
在自定义组件上,你可以像在任何普通元素上一样使用 v-for
。
<my-component v-for="item in items" :key="item.id"></my-component>
2.2.0+ 的版本里,当在组件上使用 v-for
时,key
现在是必须的。
然而,任何数据都不会被自动传递到组件里,因为组件有自己独立的作用域。为了把迭代数据传递到组件里,我们要使用 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 todo</label> <input v-model="newTodoText" id="new-todo" placeholder="E.g. Feed the cat" > <button>Add</button> </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"
属性。这种做法在使用 DOM 模板时是十分必要的,因为在 <ul>
元素内只有 <li>
元素会被看作有效内容。这样做实现的效果与 <todo-item>
相同,但是可以避开一些潜在的浏览器解析错误。查看 DOM 模板解析说明 来了解更多信息。
Vue.component('todo-item', { template: '\ <li>\ {{ title }}\ <button v-on:click="$emit(\'remove\')">Remove</button>\ </li>\ ', 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 = '' } } })
7、事件处理
7.1、监听事件
可以用 v-on
指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。
7.2、事件处理方法
然而许多事件处理逻辑会更为复杂,所以直接把 JavaScript 代码写在 v-on
指令中是不可行的。因此 v-on
还可以接收一个需要调用的方法名称。
7.3、内联处理器中的方法
除了直接绑定到一个方法,也可以在内联 JavaScript 语句中调用方法
有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event
把它传入方法
7.4、事件修饰符
在事件处理程序中调用 event.preventDefault()
或 event.stopPropagation()
是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。
为了解决这个问题,Vue.js 为 v-on
提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。
.stop 停止
.prevent 保护
.capture 捕获
.self 自己
.once 一次
.passive 被动地
7.5、按键修饰符
7.6、系统修饰键
7.7、为什么在HTML中监听事件?
8、表单输入绑定
9、组件基础
9.1、基础实例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>button-coomponent</title> <script src="https://cdn.jsdelivr.net/npm/vue"></script> </head> <body> <div id="button-component"> <button-component></button-component> <button-component></button-component> <button-component></button-component> </div> <script> Vue.component('button-component',{ /**此处是一个函数**/ /**区别于 * data: { * count: 0 * } */ data:function(){ /**注意此处**/ return{ count:0 } }, template:'<button v-on:click="count++">You click me{{count}}次</button>' }) new Vue({ el:'#button-component' }) </script> </body> </html>
9.2、组件的组织
为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。至此,我们的组件都只是通过 Vue.component
全局注册的:
Vue.component('my-component-name', {
// ... options ...
})
全局注册的组件可以用在其被注册之后的任何 (通过 new Vue
) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。
9.3、通过Prop子组件传递数据
Prop 是你可以在组件上注册的一些自定义特性。当一个值传递给一个 prop 特性的时候,它就变成了那个组件实例的一个属性。为了给博文组件传递一个标题,我们可以用一个 props
选项将其包含在该组件可接受的 prop 列表中:
Vue.component('blog-post', { props: ['title'], template: '<h3>{{ title }}</h3>' })
一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。在上述模板中,你会发现我们能够在组件实例中访问这个值,就像访问 data
中的值一样。
一个 prop 被注册之后,你就可以像这样把数据作为一个自定义特性传递进来:
<blog-post title="My journey with Vue"></blog-post> <blog-post title="Blogging with Vue"></blog-post> <blog-post title="Why Vue is so fun"></blog-post>
//暂时遇到问题,属性展示不出来,vue实例绑定id错误导致,丢了一个#
实例,传递静态属性
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.jsdelivr.net/npm/vue"></script> </head> <body> <div id="blog-demo"> <blog-post title="My journey with Vue"></blog-post> <blog-post title="Blogging with Vue"></blog-post> <blog-post title="Why Vue is so fun"></blog-post> </div> <script> Vue.component('blog-post', { props: ['title'], template: '<h3>{{ title }}</h3>', }) new Vue({ el:"#blog-demo", }) </script> </body> </html>
使用b-bind来动态传递prop ###注意比较两个例子的区别,就可以看出 v-bind的作用
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.jsdelivr.net/npm/vue"></script> </head> <body> <div id="blog-post-demo"> <div> <blog-post v-for="post in posts" v-bind:key="post.id" v-bind:title="post.title" ></blog-post> </div> </div> <script> Vue.component("blog-post",{ props:['title'], template: '<h3>{{ title }}</h3>' }) new Vue({ el: '#blog-post-demo', data: { posts: [ { id: 1, title: 'My journey with Vue' }, { id: 2, title: 'Blogging with Vue' }, { id: 3, title: 'Why Vue is so fun' } ] } }) </script> </body> </html>
9.4、单个根元素
常见错误:
Vue 会显示一个错误,并解释道 every component must have a single root element (每个组件必须只有一个根元素)。你可以将模板的内容包裹在一个父元素内,来修复这个问题。
关键点是:class="blog-post",
Vue.component('blog-post', { props: ['post'], template: ` <div class="blog-post"> <h3>{{ post.title }}</h3> <div v-html="post.content"></div> </div> ` })
9.5、监听子组件事件
简单实例:
#关键点 v-on:click="$emit('enlarge-text')" 子组件可以通过调用内建的 $emit 方法 并传入事件名称来触发一个事件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>blog-event</title> <script src="https://cdn.jsdelivr.net/npm/vue"></script> </head> <body> <div id="blog-posts-events-demo"> <div :style="{ fontSize: postFontSize + 'em' }"> <blog-post v-for="post in posts" v-bind:key="post.id" v-bind:post="post" v-on:enlarge-text="postFontSize += 0.1" ></blog-post> </div> </div> <script> Vue.component('blog-post', { props: ['post'], template: ` <div class="blog-post"> <h3>{{ post.title }}</h3> <button v-on:click="$emit('enlarge-text')"> Enlarge text </button> <div v-html="post.content"></div> </div> ` }) new Vue({ el: '#blog-posts-events-demo', data: { posts: [ { id: 1, title: 'My journey with Vue' }, { id: 2, title: 'Blogging with Vue' }, { id: 3, title: 'Why Vue is so fun' } ], postFontSize: 1 } }) </script> </body> </html>
- 使用事件抛出一个值
$emit
的第二个参数来提供这个值
<button v-on:click="$emit('enlarge-text', 0.1)"> Enlarge text </button>
当在父级组件监听这个事件的时候,我们可以通过
$event
访问到被抛出的这个值<blog-post ... v-on:enlarge-text="postFontSize += $event" ></blog-post>
如果这个事件处理函数是一个方法
<blog-post ... v-on:enlarge-text="onEnlargeText" ></blog-post>
这个值将会作为第一个参数传入这个方法
methods: { onEnlargeText: function (enlargeAmount) { this.postFontSize += enlargeAmount } }
- 在组件上使用 v-model
待补
9.6、通过插槽分发内容 #重点 slot 标签
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>slot-demo</title> <script src="https://cdn.jsdelivr.net/npm/vue"></script> </head> <body> <div id="slot-demo"> <alert-box>haha</alert-box> </div> <script> Vue.component('alert-box', { template: ` <div class="demo-alert-box"> <strong>Error!</strong> <slot></slot> </div> ` }) new Vue({ el:'#slot-demo', }) </script> </body> </html>
9.7、动态组件
9.8、解析DOM模板时的注意事项