在此特地声明本文是根据 vue 官方文档 学习的个人学习笔记,借用了大量的图片和文本内容,存在很多的相同。
介绍
Vue.js 是什么
Vue 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
起步
创建一个 .html
文件,你可以通过如下方式引入 Vue:
1 | <!-- 开发环境版本,包含了有帮助的命令行警告 --> |
直接下载并用 <script>
标签引入,Vue
会被注册为一个全局变量。
声明式渲染
Vue.js 的核心是一个允许采用简洁的模块语法来声明式地将数据渲染进 DOM 的系统:
1 | <div id="app"> |
1 | var app = new Vue({ |
我们已经成功创建了第一个 Vue 应用!看起来这跟渲染一个字符串模板非常类似,但是 Vue 在背后做了大量工作。
现在,数据和 DOM 已经建立了关联,所有东西都是响应式的。我们要怎么确认呢?打开你的浏览器的 JavaScript 控制台,并修改 app.message
的值,你将看到上例相应地更新。
注意:页面渲染出来后,我们就不再和 HTML 直接交互了。一个 Vue 应用会将其自己挂载到一个 DOM 元素上(对于这个例子是 #app
)然后对其进行完全控制。那个 HTML 是我们的入口,但其余都会发生在新创建的 Vue 实例内部。
除了文本插值,我们还可以像这样来绑定元素 attribute(特性):
1 | <div id="app-2"> |
1 | var app2 = new Vue({ |
这里我们遇到了一点新东西。你看到的 v-bind
attribute 被称为指令。指令带有前缀 v-
,以表示它们是 Vue 提供的特殊 attribute。可能你已经猜到了,它们会在渲染的 DOM 上应用特殊的响应式行为。在这里,该指令的意思是:”将这个元素节点的 title 属性和 Vue 实例的 message
property 保持一致”。
当鼠标悬置在块(span)上,块的 title 属性就会显示。
- property 是 DOM 中的属性,是 JavaScript 里的对象;
- attribute 是 HTML 标签上的特性,它的值只能够是字符串;
简单来讲,Attribute 就是 DOM 节点自带的属性,例如 html 中常用的 id、class、title、align 等。
而 Property 是以 DOM 元素为对象,其附加的内容,例如 childNodes、firstChild等。
条件与循环
控制切换一个元素是否显示也相当简单:
1 | <div id="app-3"> |
1 | var app3 = new Vue({ |
在控制台输入 app3.seen = false
,你会发现之前显示的消息消失了。
这个例子演示了我们不仅可以把数据绑定到 DOM 文本或 attribute,还可以绑定到 DOM 结构。此外,Vue 也提供一个强大的过渡效果系统,可以在 Vue 插入/更新/移除元素时自动应用过渡效果。
还有其它很多指令,每个都有特殊的功能。例如,v-for
指令可以绑定数组的数据来渲染一个项目列表:
1 | <div id="app-4"> |
1 | var app4 = new Vue({ |
在控制台里,输入 app4.todos.push({ text: '新项目' })
,你将会发现列表最后添加了一个新项目。
处理用户输入
为了让用户和你的应用进行交互,我们可以用 v-on
指令添加一个事件监听器,通过它调用在 Vue 实例中定义的方法:
1 | <div id="app-5"> |
1 | var app5 = new Vue({ |
注意在 reverseMessage
方法中,我们更新了应用的状态,但没有触碰 DOM——所有的 DOM 操作都由 Vue 来处理,你编写的代码只需要关注逻辑层面即可。
Vue 还提供了 v-model
指令,它能轻松实现表单输入和应用状态之间的双向绑定。
1 | <div id="app-6"> |
1 | var app6 = new Vue({ |
组件化应用构建
组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。仔细想想,几乎任何类型的应用界面都可以抽象为一个组件树:
在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例。在 Vue 中注册组件很简单:
1 | // 定义名为 todo-item 的新组件 |
现在你可以用它构建另一个组件模板:
1 | <ol> |
但是这样会为每个待办项渲染同样的文本,这样看起来并不炫酷。我们应该能从父作用域将数据传到子组件才对。让我们来修改一下组件的定义,使之能够接受一个 prop:
1 | Vue.component('todo-item', { |
现在,我们可以使用 v-bind
指令将待办项传到循环输出的每个组件中:
1 | <div id="app-7"> |
1 | Vue.component('todo-item', { |
尽管这只是一个刻意设计的例子,但是我们已经设法将应用分割成了两个更小的单元。子单元通过 prop 接口与父单元进行了良好的解耦。我们现在可以进一步改进 <todo-item>
组件,提供更为复杂的模板和逻辑,而不会影响到父单元。
在一个大型应用中,有必要将整个应用程序划分为组件,以使开发更易管理。在后续教程中我们将详述组件,不过这里有一个(假想的)例子,以展示使用了组件的应用模板是什么样的:
1 | <div id="app"> |
与自定义元素的关系
你可能已经注意到 Vue 组件非常类似于自定义元素——它是 Web 组件规范的一部分,这是因为 Vue 的组件语法部分参考了该规范。例如 Vue 组件实现了 Slot API 与 is
attribute。但是,还是有几个关键差别:
- Web Components 规范已经完成并通过,但未被所有浏览器原生实现。相比之下,Vue 组件不需要任何 polyfill,并且在所有支持的浏览器之下表现一致。必要时,Vue 组件也可以包装于原生自定义元素之内。
- Vue 组件提供了纯自定义元素所不具备的一些重要功能,最突出的是跨组件数据流、自定义事件通信以及构建工具集成。
虽然 Vue 内部没有使用自定义元素,不过在应用使用自定义元素、或以自定义元素形式发布时,依然有很好的互操作性。Vue CLI 也支持将 Vue 组件构建成为原生的自定义元素。
Vue 实例
创建一个 Vue 实例
每个 Vue 应用都是通过 Vue
函数创建一个新的 Vue 实例开始的:
1 | var vm = new Vue({ |
虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm
(ViewModel 的缩写)这个变量名表示 Vue 实例。
当创建一个 Vue 实例时,你可以传入一个选项对象。这篇教程主要描述的就是如何使用这些选项来创建你想要的行为。作为参考,你可以在 API 文档 中浏览完整的选项列表。
一个 Vue 应用由一个通过 new Vue
创建的根 Vue 实例,以及可选的嵌套的、可复用的组件树组成。举个例子,一个 todo 应用的组件树可以是这样的:
1 | 根实例 |
我们会在稍后的组件系统章节具体展开。不过此时,你只需要明白所有的 Vue 组件都是 Vue 实例,并且接受相同的选项对象(一些根实例特有的选项除外)。
数据与方法
当一个 Vue 实例被创建时,它将 data 对象中的所有的 property 加入到 Vue 的响应式系统中。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。
1 | // 我们的数据对象 |
当这些数据改变时,视图会进行重渲染。值得注意的是只有当实例被创建时就已经存在于 data 中的 property 才是响应式的。
如果你添加一个新的 property,比如:
1 | vm.b = 'hi' |
那么对 b 的改动将不会触发任何视图的更新。如果你知道你会在晚些时候需要一个 property,但是一开始它为空或不存在,那么你仅需要设置一些初始值。比如:
1 | data: { |
这里唯一的例外是使用 Object.freeze()
,这会阻止修改现有的 property,意味着响应式系统无法再追踪变化。
1 | var obj = { |
1 | <div id="app"> |
除了数据 property,Vue 实例还提供了一些有用的实例 property 与方法。它们都有前缀 $
,以便与用户定义的 property 区分开来。例如:
1 | var data = { a: 1 } |
你可以在 API 参考中查阅到完整的实例 property 和方法的列表。
实例生命周期钩子
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中,会运行一些叫做生命周期钩子的函数,允许用户在不同阶段添加自己的代码。
比如 created
钩子可以用来在一个实例被创建之后执行代码:
1 | new Vue({ |
也有一些其它的钩子,在实例生命周期的不同阶段被调用,如 mounted
、updated
、destroyed
。生命周期钩子的 this
上下文指向调用它的 Vue 实例。
不要在选项 property 或回调上使用箭头函数,比如
created: () => console.log(this.a)
或vm.$watch('a', newValue => this.myMethod())
。因为箭头函数并没有
this
,this
会作为变量一直向上级词法作用域查找,直至找到为止,经常导致
Uncaught TypeError: Cannot read property of undefined
Uncaught TypeError: this.myMethod is not a function
之类的错误。
生命周期图示
下图展示了实例的生命周期:
模板语法
Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 和底层 Vue 实例中的数据绑定。所有 Vue.js 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。
在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。
如果你熟悉虚拟 DOM 并且偏爱 JavaScript 的原始力量,你也可以不用模板,直接写渲染 (render) 函数,使用可选的 JSX 语法。
插值
文本
数据绑定最常见的形式就是使用“Mustache”语法(双大括号)的文本插值:
1 | <span>Message: {{ msg }}</span> |
Mustache 标签将会被替代为对应数据对象上 msg
property 的值。无论何时,绑定的数据对象上 msg
property 发生了改变,插值处的内容都会更新。
通过使用 v-once
指令,你能执行一次性地插值,当数据改变时,插值处的内容不会更新。
1 | <span v-once>这个将不会改变: {{ msg }}</span> |
v-once
:只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。
原始 HTML
双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用 v-html
指令:
1 | <p id="app1">Using mustaches: {{ rawHtml }}</p> |
这个 span
中的内容会被替换为 property 值 rawHtml
,它直接作为 HTML——会忽略解析 property 值中的数据绑定。注意,你不能使用 v-html
来复合局部模板,因为 Vue 不是基于字符串的模板引擎。反之,对于用户界面(UI),组件更适合作为可重用和可组合的基本单位。
不要在站点上动态渲染任意的 HTML,因为它很容易导致 XSS 攻击。
Attribute
Mustache 语法不能作用在 HTML attribute 上,遇到这种情况应该使用 v-bind
指令:
1 | <div v-bind:id="dynamicId"></div> |
对于布尔 attribute,它们只要存在就意味着值为 true
。例如:
1 | <button v-bind:disabled="isButtonDisabled">Button</button> |
如果 isButtonDisabled
的值是 null
、undefined
或 false
,那么 disabled
attribute 就不会被包含在渲染出来的 <button>
元素中。
使用 JavaScript 表达式
迄今为止,在我们的模板中,我们一直都只绑定简单的 property 键值。但实际上,对于所有的数据绑定,Vue.js 都提供了完全的 JavaScript 表达式支持。
1 | {{ number + 1 }} |
这些表达式会在所属 Vue 实例的数据作用域下作为 JavaScript 被解析。不过,每个绑定都只能包含单个表达式。
下面的例子不会生效:
1 |
|
模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如 Math
和 Date
。你不应该在模板表达式中试图访问用户定义的全局变量。
指令
指令(Directives)是带有 v-
前缀的特殊 attribute。指令 attribute 的值预期是单个 JavaScript 表达式(v-for
是特殊情况)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。回顾我们在介绍中看到的例子:
1 | <p v-if="seen">现在你看到我了</p> |
这里,v-if
指令将根据表达式 seen
的值的真假来插入/移除 <p>
元素。
参数
一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如,v-bind
指令可以用于响应式地更新 HTML attribute:
1 | <a v-bind:href="url">...</a> |
在这里 href
是参数,告知 v-bind
指令将该元素的 href
attribute 与表达式 url
的值绑定。
另一个例子是 v-on
指令,它用于监听 DOM 事件:
1 | <a v-on:click="doSomething">...</a> |
在这里参数是监听的事件名。
动态参数
从 2.6.0 开始,可以用方括号括起来的 JavaScript 表达式作为一个指令的参数:
1 | <!-- 注意,参数表达式的写法存在一些约束。--> |
这里的 attributeName
会被作为一个 JavaScript 表达式进行动态求值,求得的值将会作为最终的参数来使用。例如,如果你的 Vue 实例有一个 data
property attributeName
,其值为 "href"
,那么这个绑定将等价于 v-bind:href
。
同样地,你可以使用动态参数为一个动态的事件名绑定处理函数:
1 | <a v-on:[eventName]="doSomething"> ... </a> |
在这个示例中,当 eventName
的值为 "focus"
时,v-on:[eventName]
将等价于 v-on:focus
。
对动态参数的值的约束
动态参数预期会求出一个字符串,异常情况下值为 null
。这个特殊的 null
值可以被显性地用于移除绑定;任何其它非字符串类型的值都将会触发一个警告。
对动态参数表达式的约束
动态参数表达式有一些语法约束,因为某些字符,如空格和引号,放在 HTML attribute 名里是无效的。例如:
1 | <!-- 这会触发一个编译警告 --> |
变通的办法是使用没有空格或引号的表达式,或用计算属性替代这种复杂表达式。
在 DOM 中使用模板时(直接在一个 HTML 文件里撰写模板),还需要避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写:
1 | <!-- |
修饰符
修饰符(modifier)是以半角句号 .
指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent
修饰符告诉 v-on
指令对于触发的事件调用 event.preventDefault()
:
1 | <form v-on:submit.prevent="onSubmit">...</form> |
在接下来对 v-on
和 v-for
等功能的探索中,你会看到修饰符的其它例子。
缩写
v-
前缀作为一种视觉提示,用来识别模板中 Vue 特定的 attribute。当你在使用 Vue.js 为现有标签添加动态行为(dynamic behavior)时,v-
前缀很有帮助,然而,对于一些频繁用到的指令来说,就会感到使用繁琐。同时,在构建由 Vue 管理所有模板的单页面应用程序(SPA - single page application)时,v-
前缀也变得没那么重要了。因此,Vue 为 v-bind
和 v-on
这两个最常用的指令,提供了特定简写:
v-bind
缩写
1 | <!-- 完整语法 --> |
v-on
缩写
1 | <!-- 完整语法 --> |
它们看起来可能与普通的 HTML 略有不同,但 :
与 @
对于 attribute 名来说都是合法字符,在所有支持 Vue 的浏览器都能被正确地解析。而且,它们不会出现在最终渲染的标记中。
计算属性和侦听器
计算属性
模块内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模块中放入太多的逻辑会让模板过重且难以维护。例如:
1 | <div id="example"> |
在这个地方,模板不再是简单的声明式逻辑。你必须看一小会儿才能意识到,这里是要显示变量 message
的翻转字符串。当你想要在模板中的多处包含此翻转字符串时,就会更加难以处理。
所以,对于任何复杂逻辑,你都应当使用计算属性(computed)。
基础例子
1 | <div id="example"> |
1 | var vm = new Vue({ |
这里我们声明了一个计算属性 reversedMessage
。我们提供的函数将用作 property vm.reversedMessage
的 getter 函数:
1 | console.log(vm.reversedMessage) // => 'olleH' |
你可以像绑定普通 property 一样在模板中绑定计算属性。Vue 知道 vm.reversedMessage
依赖于 vm.message
,因此当 vm.message
发生改变时,所有依赖 vm.reversedMessage
的绑定也会更新。而且最妙的是我们已经以声明的方式创建了这种依赖关系:计算属性的 getter 函数是没有副作用(side effect)的,这使它更易于测试和理解。
计算属性缓存 vs 方法
你可能已经注意到我们可以通过在表达式中调用方法来达到同样的效果:
1 | <p> |
1 | // 在组件中 |
我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message
还没有发生改变,多次访问 reversedMessage
计算属性会立即返回之前的计算结果,而不必再次执行函数。
这也同样意味着下面的计算属性将不再更新,因为 Date.now()
不是响应式依赖:
1 | computed: { |
相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。
我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A。如果没有缓存,我们将不可避免的多次执行 A 的 getter。
计算属性 vs 侦听属性
Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。但当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch
——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch
回调。细想一下这个例子:
1 | <div id="demo">{{ fullName }}</div> |
1 | var vm = new Vue({ |
上面代码是命令式的且重复的。将它与计算属性的版本进行比较:
1 | var vm = new Vue({ |
相比,计算属性更加简介。
计算属性的 setter
计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:
1 | computed: { |
现在再运行 vm.fullName = 'John Doe'
时,setter 会被调用,vm.firstName
和 vm.lastName
也会相应地被更新。
侦听器
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch
选项提供了一个更通用的方法,来响应数据的变化。
当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。例如:
1 | <div id="watch-example"> |
1 | <!-- 因为 AJAX 库和通用工具的生态已经相当丰富,Vue 核心代码没有重复 --> |
在这个示例中,使用 watch
选项允许我们执行异步操作(访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
Class 与 Style 绑定
操作元素的 class 列表和内联样式是数据绑定的一个常见需求。因为它们都是 attribute,所以我们可以用 v-bind
处理它们:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错。因此,在将 v-bind
用于 class
和 style
时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组。
绑定 HTML Class
对象语法
我们可以传给 v-bind:class
一个对象,以动态地切换 class:
1 | <div v-bind:class="{ active: isActive }"></div> |
上面的语法表示 active
这个 class 存在与否将取决于数据 property isActive
的真值。
你可以在对象中传入更多字段来动态切换多个 class。此外,v-bind:class
指令也可以与普通的 class attribute 共存。例如:
1 | <div |
1 | data: { |
结果渲染为:
1 | <div class="static active"></div> |
当 isActive
或者 hasError
变化时,class 列表将相应地更新。
绑定的数据对象不一定要内联定义在模板里:
1 | <div v-bind:class="classObject"></div> |
1 | data: { |
渲染的结果和上面一样。我们也可以在这里绑定一个返回对象的计算属性。这是一个常用且强大的模式:
1 | <div v-bind:class="classObject"></div> |
1 | data: { |
数组语法
我们可以把一个数组传给 v-bind:class
,以应用一个 class 列表:
1 | <div v-bind:class="[activeClass, errorClass]"></div> |
1 | data: { |
渲染为:
1 | <div class="active text-danger"></div> |
如果你也想根据条件切换列表中的 class,可以用三元表达式:
1 | <div v-bind:class="[isActive ? activeClass : '', errorClass]"></div> |
这样写将始终添加 errorClass
,但是只有在 isActive
是 truthy(真值) 时才添加 activeClass
。
不过,当有多个条件 class 时这样写有些繁琐。所以在数组语法中也可以使用对象语法:
1 | <div v-bind:class="[{ active: isActive }, errorClass]"></div> |
用在组件上
当在一个自定义组件上使用 class
property 时,这些 class 将被添加到该组件的根元素上面。这个元素上已经存在的 class 不会被覆盖。
例如,如果你声明了这个组件:
1 | Vue.component('my-component', { |
然后在使用它的时候添加一些 class:
1 | <my-component class="baz boo"></my-component> |
HTML 将被渲染为:
1 | <p class="foo bar baz boo">Hi</p> |
对于带数据绑定 class 也同样适用:
1 | <my-component v-bind:class="{ active: isActive }"></my-component> |
当 isActive
为 truthy 时,HTML 将被渲染成为:
1 | <p class="foo bar active">Hi</p> |
绑定内联样式
对象语法
v-bind:style
的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS property 名可以用驼峰式(camelCase)或短横线分隔(kebab-case,记得用引号括起来)来命名:
1 | <div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div> |
1 | data: { |
直接绑定到一个样式对象通常更好,这会让模板更清晰:
1 | <div v-bind:style="styleObject"></div> |
1 | data: { |
同样的,对象语法常常结合返回对象的计算属性使用。
数组语法
v-bind:style
的数组语法可以将多个样式对象应用到同一个元素上:
1 | <div v-bind:style="[baseStyles, overridingStyles]"></div> |
自动添加前缀
当 v-bind:style
使用需要添加浏览器引擎前缀的 CSS property 时,如 transform
,Vue.js 会自动侦测并添加相应的前缀。
多重值
从 2.3.0 起你可以为 style
绑定中的 property 提供一个包含多个值的数组,常用于提供多个带前缀的值,例如:
1 | <div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div> |
这样写只会渲染数组中最后一个被浏览器支持的值。在本例中,如果浏览器支持不带浏览器前缀的 flexbox,那么就只会渲染 display: flex
。
条件渲染
v-if
v-if
指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 的时候被渲染。
1 | <h1 v-if="awesome">Vue is awesome!</h1> |
也可以用 v-else
添加一个“else 块”:
1 | <h1 v-if="awesome">Vue is awesome!</h1> |
在 <template>
元素上使用 v-if
条件渲染分组
因为 v-if
是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢?此时可以把一个 <template>
元素当做不可见的包裹元素,并在上面使用 v-if
。最终的渲染结果将不包含 <template>
元素。
1 | <template v-if="ok"> |
v-else
你可以使用 v-else
指令来表示 v-if
的“else 块”:
1 | <div v-if="Math.random() > 0.5"> |
v-else
元素必须紧跟在带 v-if
或者 v-else-if
的元素的后面,否则它将不会被识别。
v-else-if
在 2.1.0 新增 v-else-if
,充当 v-if
的“else-if 块”,可以连续使用:
1 | <div v-if="type === 'A'"> |
类似于 v-else
,v-else-if
也必须紧跟在带 v-if
或者 v-else-if
的元素之后。
用 key
管理可复用的元素
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。
这么做除了使 Vue 变得非常快之外,还有其它一些好处。例如,如果你允许用户在不同的登录方式之间切换:
1 | <template v-if="loginType === 'username'"> |
那么在上面的代码中切换 loginType
将不会清除用户已经输入的内容。因为两个模板使用了相同的元素,<input>
不会被替换掉——仅仅是替换了它的 placeholder
。
这样也不总是符合实际需求,所以 Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key
attribute 即可:
1 | <template v-if="loginType === 'username'"> |
现在,每次切换时,输入框都会被重新渲染,原先输入文本框中的数据也会被刷新。
而 <label>
元素仍然会被高效地复用,因为它们没有添加 key
attribute。
v-show
另一个用于根据条件展示元素的选项是 v-show
指令。用法大致一样:
1 | <h1 v-show="ok">Hello!</h1> |
不同的是带有 v-show
的元素始终会被渲染并保留在 DOM 中。v-show
只是简单地切换元素的 CSS property display
。
注意,
v-show
不支持<template>
元素。
v-if
vs v-show
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show
较好;如果在运行时条件很少改变,则使用 v-if
较好。
v-if
与 v-for
一起使用
不推荐同时使用 v-if
和 v-for
。当 v-if
与 v-for
一起使用时,v-for
具有比 v-if
更高的优先级。
列表渲染
用 v-for
把一个数组对应为一组元素
我们可以用 v-for
指令基于一个数组来渲染一个列表。v-for
指令需要使用 item in items
形式的特殊语法,其中 items
是源数据数组,而 item
则是被迭代的数组元素的别名。
1 | <ul id="example-1"> |
1 | var example1 = new Vue({ |
结果:
- Foo
- Bar
在 v-for
块中,我们可以访问所有父作用域的 property。v-for
还支持一个可选的第二个参数,即当前项的索引。
1 | <ul id="example-2"> |
1 | var example2 = new Vue({ |
你也可以用 of
替代 in
作为分隔符,它更接近 JavaScript 迭代器的语法:
1 | <div v-for="item of items"></div> |
在 v-for
里使用对象
你也可以用 v-for
来遍历一个对象的 property。
1 | <ul id="v-for-object" class="demo"> |
1 | new Vue({ |
结果:
- How to do lists in Vue
- Jane Doe
- 2016-04-10
你也可以提供第二个的参数为 property 名称 (也就是键名):
1 | <div v-for="(value, name) in object"> |
还可以用第三个参数作为索引:
1 | <div v-for="(value, name, index) in object"> |
在遍历对象时,会按 Object.keys()
的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下都一致。
维护状态
当 Vue 正在更新使用 v-for
渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素的值,以确保它们在每个索引位置正确渲染。
这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态(例如:表单输入值)的列表渲染输出。
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一的 key
attribute:
1 | <div v-for="item in items" v-bind:key="item.id"> |
建议尽可能在使用 v-for
时提供 key
attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。
key
是 Vue 识别节点的一个通用机制,它并不仅仅与 v-for
特别关联,它还具有其它用途。
不要使用对象或数组之类的非基本类型值作为 v-for
的 key
,请用字符串或数值类型的值。
数组更新检测
变更方法
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
你可以打开控制台,然后对前面例子的 items
数组尝试调用变更方法。比如 example1.items.push({ message: 'Baz' })
。
替换数组
变更方法,顾名思义,会变更调用了这些方法的原始数组。相比之下,也有非变更方法,例如 filter()
、concat()
和 slice()
。它们不会变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组:
1 | example1.items = example1.items.filter(function (item) { |
你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。
注意:由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。
显示过滤/排序后的结果
有时,我们想要显示一个数组经过过滤或排序后的版本,而不实际变更或重置原始数据。在这种情况下,可以创建一个计算属性,来返回过滤或排序后的数组。
例如:
1 | <li v-for="n in evenNumbers">{{ n }}</li> |
1 | data: { |
在计算属性不适用的情况下(例如,在这个嵌套 v-for
循环中,需要处理的数据依赖于外层循环的返回值),你可以这样子使用:
1 | <ul v-for="set in sets"> |
1 | data: { |
在 v-for
里使用值范围
v-for
也可以接受整数。在这种情况下,它会把模板重复对应次数。
1 | <div> |
结果:
在 <template>
上使用 v-for
类似于 v-if
,你也可以利用带有 v-for
的 <template>
来循环渲染一段包含多个元素的内容。比如:
1 | <ul> |
v-for
与 v-if
一同使用
注意:不推荐在同一元素上使用
v-if
和v-for
。
当它们处于同一节点,v-for
的优先级比 v-if
更高,这意味着 v-if
将分别重复运行于每个 v-for
循环中。当你只想为部分项渲染节点时,这种优先级的机制会十分有用,如下:
1 | <li v-for="todo in todos" v-if="!todo.isComplete"> |
上面的代码将只渲染未完成的 todo。
如果你的目的是有条件地跳过循环的执行,那么可以将 v-if
置于外层元素(或 <template>
)上。如:
1 | <ul v-if="todos.length"> |
在组件上使用 v-for
在自定义组件上,你可以像在任何普通元素上一样使用 v-for
。
1 | <my-component v-for="item in items" :key="item.id"></my-component> |
2.2.0+ 的版本里,当在组件上使用
v-for
时,key
是必须的。
然而,任何数据都不会被自动传递到组件里,因为组件有自己独立的作用域。为了把迭代数据传递到组件里,我们要使用 prop:
1 | <my-component |
不自动将 item
注入到组件里的原因是,这会使得组件与 v-for
的运作紧密耦合。明确组件数据的来源能够使组件在其他场合重复使用。
下面是一个简单的 todo 列表的完整例子:
1 | <div id="todo-list-example"> |
注意这里的 is="todo-item"
attribute。这种做法在使用 DOM 模板时是十分必要的,因为在 <ul>
元素内只有 <li>
元素会被看作有效内容。这样做实现的效果与 <todo-item>
相同,但是可以避开一些潜在的浏览器解析错误。
1 | Vue.component('todo-item', { |
事件处理
监听事件
可以用 v-on
指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。
示例:
1 | <div id="example-1"> |
1 | var example1 = new Vue({ |
结果:
事件处理方法
对于更复杂的事件处理逻辑,直接把 JavaScript 代码写在 v-on
指令中是不可行的。因此 v-on
可以接收一个需要调用的方法名称。
示例:
1 | <div id="example-2"> |
1 | var example2 = new Vue({ |
内联处理器中的方法
除了直接绑定到一个方法,也可以在内联 JavaScript 语句中调用方法:
1 | <div id="example-3"> |
1 | new Vue({ |
结果:
有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event
把它传入方法:
1 | <button v-on:click="warn('Form cannot be submitted yet.', $event)"> |
1 | // ... |
事件修饰符
在事件处理程序中调用 event.preventDefault()
或 event.stopPropagation()
是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。
为了解决这个问题,Vue.js 为 v-on
提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。
.stop
.prevent
.capture
.self
.once
.passive
1 | <!-- 阻止单击事件继续传播 --> |
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self
会阻止所有的点击,而 v-on:click.self.prevent
只会阻止对元素自身的点击。
2.1.4 新增
1 | <!-- 点击事件将只会触发一次 --> |
不像其它只能对原生的 DOM 事件起作用的修饰符,.once
修饰符还能被用到自定义的组件事件上。
2.3.0 新增
Vue 还对应 addEventListener
中的 passive 选项提供了 .passive
修饰符。
1 | <!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 --> |
这个 .passive
修饰符尤其能够提升移动端的性能。
不要把 .passive
和 .prevent
一起使用,因为 .prevent
将会被忽略,同时浏览器可能会向你展示一个警告。请记住,.passive
会告诉浏览器你不想阻止事件的默认行为。
按键修饰符
在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on
在监听键盘事件时添加按键修饰符:
1 | <!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` --> |
你可以直接将 KeyboardEvent.key
暴露的任意有效按键名转换为 kebab-case 来作为修饰符。
1 | <input v-on:keyup.page-down="onPageDown"> |
在上述示例中,处理函数只会在 $event.key
等于 PageDown
时被调用。
按键码
keyCode
的事件用法已经被废弃了,可能不会被最新的浏览器支持。
不过对于 Vue 来说,使用 keyCode
attribute 也是允许的:
1 | <input v-on:keyup.13="submit"> |
为了在必要的情况下支持旧浏览器,Vue 提供了绝大多数常用的按键码的别名:
.enter
.tab
.delete
(捕获“删除”和“退格”键).esc
.space
.up
.down
.left
.right
有一些按键(.esc
以及所有的方向键)在 IE9 中有不同的 key
值, 如果你想支持 IE9,这些内置的别名应该是首选。
你还可以通过全局 config.keyCodes
对象自定义按键码的别名:
1 | // 可以使用 v-on:keyup.f1 |
系统修饰键
2.1.0 新增
可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。
.ctrl
.alt
.shift
.meta
注意:在 Mac 系统键盘上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞)。在 Sun 操作系统键盘上,meta 对应实心宝石键 (◆)。
例如:
1 | <!-- Alt + C --> |
注意:修饰键与常规按键不同,在和
keyup
事件一起用时,事件触发时修饰键必须处于按下状态,单单释放alt
不会触发事件。
.exact
修饰符
2.5.0 新增
.exact
修饰符允许你控制由精确的系统修饰符组合触发的事件。
1 | <!-- 即使 Alt 或 Shift 被一同按下时也会触发 --> |
鼠标按钮修饰符
2.2.0 新增
.left
.right
.middle
这些修饰符会限制处理函数仅响应特定的鼠标按钮。
为什么在 HTML 中监听事件?
你可能注意到这种事件监听的方式违背了关注点分离(separation of concern)这个长期以来的优良传统。但不必担心,因为所有的 Vue.js 事件处理方法和表达式都严格绑定在当前视图的 ViewModel 上,它不会导致任何维护上的困难。实际上,使用 v-on
有几个好处:
扫一眼 HTML 模板便能轻松定位在 JavaScript 代码里对应的方法。
因为你无须在 JavaScript 里手动绑定事件,你的 ViewModel 代码可以是非常纯粹的逻辑,和 DOM 完全解耦,更易于测试。
当一个 ViewModel 被销毁时,所有的事件处理器都会自动被删除。你无须担心如何清理它们。
表单输入绑定
基础用法
你可以用 v-model
指令在表单 <input>
、<textarea>
及 <select>
元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model
本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
v-model
会忽略所有表单元素的value
、checked
、selected
attribute 的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的data
选项中声明初始值。
v-model
在内部为不同的输入元素使用不同的 property 并根据这些 property 抛出不同的事件:
- text 和 textarea 元素使用
value
property 和input
事件; - checkbox 和 radio,单个选项使用
checked
property,多个选项输出value
property 的数组,事件使用change
事件; - select 字段将
value
作为 prop 并将change
作为事件。
对于需要使用输入法(如中文、日文、韩文等)的语言,你会发现
v-model
不会在输入法组合文字过程中得到更新。如果你也想处理这个过程,请使用input
事件。
文本
1 | <input v-model="message" placeholder="edit me"> |
多行文本
1 | <span>Multiline message is:</span> |
在文本区域插值(<textarea>{{text}}</textarea>
)并不会生效,应用 v-model
来代替。
复选框
单个复选框,绑定到布尔值:
1 | <input type="checkbox" id="checkbox" v-model="checked"> |
v-model
指令将 data 对象中的 checked 属性和 checkbox 输入元素的 checked
property 绑定在了一起。
多个复选框,绑定到同一个数组:
1 | <input type="checkbox" id="jack" value="Jack" v-model="checkedNames"> |
1 | new Vue({ |
单选按钮
1 | <div id="example-4"> |
1 | new Vue({ |
选择框
单选时:
1 | <div id="example-5"> |
1 | new Vue({ |
因为没有 value
attribute,selected 使用元素标签内的内容。
如果
v-model
表达式的初始值未能匹配任何选项,<select>
元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐像上面这样提供一个值为空的禁用选项。
多选时 (绑定到一个数组):
1 | <div id="example-6"> |
1 | new Vue({ |
用 v-for
渲染的动态选项:
1 | <select v-model="selected"> |
1 | new Vue({ |
值绑定
对于单选按钮,复选框及选择框的选项,v-model
绑定的值通常是静态字符串 (对于复选框也可以是布尔值):
1 | <!-- 当选中时,picked 为字符串 "a" --> |
但是有时我们可能想把值绑定到 Vue 实例的一个动态 property 上,这时可以用 v-bind
实现,并且这个 property 的值可以不是字符串。
复选框
1 | <input |
1 | // 当选中时 |
这里的 true-value
和 false-value
attribute 并不会影响输入控件的 value
attribute,因为浏览器在提交表单时并不会包含未被选中的复选框。如果要确保表单中这两个值中的一个能够被提交,(即“yes”或“no”),请换用单选按钮。
单选按钮
1 | <input type="radio" v-model="pick" v-bind:value="a"> |
1 | // 当选中时 |
选择框的选项
1 | <select v-model="selected"> |
1 | // 当选中时 |
修饰符
.lazy
在默认情况下,v-model
在每次 input
事件触发后将输入框的值与数据进行同步(除了输入法组合文字时)。你可以添加 lazy
修饰符,从而转为在 change
事件之后进行同步:
1 | <!-- 在“change”时而非“input”时更新 --> |
input
事件是指输入框产生新的输入;change
事件是指输入框的值确定发生改变(键入回车键,确认输入)。
.number
如果想自动将用户的输入值转为数值类型,可以给 v-model
添加 number
修饰符:
1 | <input v-model.number="age" type="number"> |
这通常很有用,因为即使在 type="number"
时,HTML 输入元素的值也总会返回字符串——如果这个值无法被 parseFloat()
解析,则会返回原始的值。
.trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model
添加 trim
修饰符:
1 | <input v-model.trim="msg"> |
在组件上使用 v-model
HTML 原生的输入元素类型并不总能满足需求。幸好,Vue 的组件系统允许你创建具有完全自定义行为且可复用的输入组件。这些输入组件甚至可以和 v-model
一起使用!
组件基础
基本示例
这里有一个 Vue 组件的示例:
1 | // 定义一个名为 button-counter 的新组件 |
组件是可复用的 Vue 实例,且带有一个名字:在这个例子中是 <button-counter>
。我们可以在一个通过 new Vue
创建的 Vue 根实例中,把这个组件作为自定义元素来使用:
1 | <div id="components-demo"> |
1 | new Vue({ el: '#components-demo' }) |
因为组件是可复用的 Vue 实例,所以它们与 new Vue
接收相同的选项,例如 data
、computed
、watch
、methods
以及生命周期钩子等。仅有的例外是像 el
这样根实例特有的选项。
组件的复用
你可以将组件进行任意次数的复用:
1 | <div id="components-demo"> |
注意当点击按钮时,每个组件都会各自独立维护(且只是维护)它的 count
。因为你每用一次组件,就会有一个它的新实例被创建。
data
必须是一个函数
<button-counter>
组件的 data
选项并不是直接提供一个对象,而是一个函数。由于自定义组件的 data 选项必须是一个函数,每个实例得以维护一份被返回对象的独立拷贝:
1 | data: function () { |
如果 Vue 没有这条规则,点击一个按钮就可能会像如下代码一样影响到其它所有实例:
组件的组织
通常一个应用会以一棵嵌套的组件树的形式来组织:
例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。
为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。
目前为止,我们的组件都只是通过 Vue.component
全局注册的:
1 | Vue.component('my-component-name', { |
全局注册的组件可以用在其被注册之后的任何(通过 new Vue
)新创建的 Vue 根实例中,包括其组件树中的所有子组件的模板中。
通过 Prop 向子组件传递数据
当创建一个博文组件时,我们该如何向这个组件传递某一篇博文的标题或内容?关于这个问题,我们可以使用 props 选项。
Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property。
为了给博文组件传递一个标题,我们可以用一个 props
选项将其包含在该组件可接受的 prop 列表中:
1 | Vue.component('blog-post', { |
一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。在上述模板中,你会发现我们能够在组件实例中访问这个值,就像访问 data
中的值一样。
一个 prop 被注册之后,你就可以像这样把数据作为一个自定义 attribute 传递进来:
1 | <blog-post title="My journey with Vue"></blog-post> |
然而在一个典型的应用中,你可能在 data
里有一个博文的数组:
1 | new Vue({ |
并想要为每篇博文渲染一个组件:
1 | <blog-post |
如上所示,我们可以使用 v-bind
来动态传递 prop。
单个根元素
当构建一个 <blog-post>
组件时,你的模板最终会包含的东西远不止一个标题:
1 | <h3>{{ title }}</h3> |
最最起码,你会包含这篇博文的正文:
1 | <h3>{{ title }}</h3> |
然而如果你在模板中尝试这样写,Vue 会显示一个错误,并解释道 **every component must have a single root element (每个组件必须只有一个根元素)**。你可以将模板的内容包裹在一个父元素内,来修复这个问题,例如:
1 | <div class="blog-post"> |
看起来当组件变得越来越复杂的时候,我们的博文不只需要标题和内容,还需要发布日期、评论等等。为每个相关的信息定义一个 prop 会变得很麻烦:
1 | <blog-post |
所以是时候重构一下这个 <blog-post>
组件了,让它变成接受一个单独的 post
prop:
1 | <blog-post |
1 | Vue.component('blog-post', { |
上述的这个和一些接下来的示例使用了 JavaScript 的模板字符串来让多行的模板更易读,它们在 IE 下并没有被支持。
现在,不论何时为 post
对象添加一个新的 property,它都会自动地在 <blog-post>
内可用。
监听子组件事件
在我们开发 <blog-post>
组件时,它的一些功能可能要求我们和父级组件进行沟通。例如我们可能会引入一个辅助功能来放大博文的字号,同时让页面的其它部分保持默认的字号。
在其父组件中,我们可以通过添加一个 postFontSize
数据 property 来支持这个功能:
1 | new Vue({ |
它可以在模板中用来控制所有博文的字号:
1 | <div id="blog-posts-events-demo"> |
现在我们在每篇博文正文之前添加一个按钮来放大字号:
1 | Vue.component('blog-post', { |
当点击这个按钮时,我们需要告诉父级组件放大所有博文的文本。Vue 实例提供了一个自定义事件的系统来解决这个问题。
父级组件可以像处理 native DOM 事件一样通过 v-on
监听子组件实例的任意事件:
1 | <blog-post |
同时子组件可以通过调用内建的 $emit
方法并传入事件名称来触发一个事件:
1 | <button v-on:click="$emit('enlarge-text')"> |
有了这个 v-on:enlarge-text="postFontSize += 0.1"
监听器,父级组件就会接收该事件并更新 postFontSize
的值。
使用事件抛出一个值
有的时候用一个事件来抛出一个特定的值是非常有用的。例如我们可能想让 <blog-post>
组件决定它的文本要放大多少。这时可以使用 $emit
的第二个参数来提供这个值:
1 | <button v-on:click="$emit('enlarge-text', 0.1)"> |
然后当在父级组件监听这个事件的时候,我们可以通过 $event
访问到被抛出的这个值:
1 | <blog-post |
或者,如果这个事件处理函数是一个方法:
1 | <blog-post |
那么这个值将会作为第一个参数传入这个方法:
1 | methods: { |
在组件上使用 v-model
自定义事件也可以用于创建支持 v-model
的自定义输入组件。记住:
1 | <input v-model="searchText"> |
等价于:
1 | <input |
当用在组件上时,v-model
则会这样:
1 | <custom-input |
为了让它正常工作,这个组件内的 <input>
必须:
- 将其
value
attribute 绑定到一个名叫value
的 prop 上 - 在其
input
事件被触发时,将新的值通过自定义的input
事件抛出
写成代码之后是这样的:
1 | Vue.component('custom-input', { |
现在 v-model
就应该可以在这个组件上完美地工作起来了:
1 | <custom-input v-model="searchText"></custom-input> |
通过插槽分发内容
和 HTML 元素一样,我们经常需要向一个组件传递内容,像这样:
1 | <alert-box> Something bad happened.</alert-box> |
这是我们希望渲染出的效果:
Vue 自定义的 <slot>
元素让这变得非常简单:
1 | Vue.component('alert-box', { |
就这么简单,我们只要在需要的地方加入插槽就行了!
动态组件
有的时候,在不同组件之间进行动态切换是非常有用的,比如在一个多标签的界面里:
上述内容可以通过 Vue 的 <component>
元素加一个特殊的 is
attribute 来实现:
1 | <!-- 组件会在 `currentTabComponent` 改变时改变 --> |
在上述示例中,currentTabComponent
可以包括
- 已注册组件的名字
- 一个组件的选项对象
你可以在这里查阅并体验完整的代码,或在这个版本了解绑定组件选项对象,而不是已注册组件名的示例。
请留意,这个 attribute 可以用于常规 HTML 元素,但这些元素将被视为组件,这意味着所有的 attribute 都会作为 DOM attribute 被绑定。对于像 value
这样的 property,若想让其如预期般工作,你需要使用 .prop
修饰器。
解析 DOM 模板时的注意事项
有些 HTML 元素,诸如 <ul>
、<ol>
、<table>
和 <select>
,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li>
、<tr>
和 <option>
,只能出现在其它某些特定的元素内部。
这会导致我们使用这些有约束条件的元素时遇到一些问题。例如:
1 | <table> |
这个自定义组件 <blog-post-row>
会被作为无效的内容提升到外部,并导致最终渲染结果出错。幸好这个特殊的 is
attribute 给了我们一个变通的办法:
1 | <table> |
需要注意的是如果我们从以下来源使用模板的话,这条限制是不存在的:
- 字符串(例如:
template: '...'
) - 单文件组件(
.vue
) <script type="text/x-template">
到这里,你需要了解的解析 DOM 模板时的注意事项——实际上也是 Vue 的全部必要内容,大概就是这些了。恭喜你!接下来还有很多东西要去学习,不过首先,我们推荐你先休息一下,试用一下 Vue,自己随意做些好玩的东西。