Vue实例
数据与方法
只有当实例被创建时就已经存在于data中的属性才是响应式的。
如果你新添加一个属性,那么此改动不会触发任何视图的更新。
唯一例外:Object.freeze()
将会阻止修改现有属性,响应系统无法追踪变化。
var obj = {
foo: 'bar'
}
Object.freeze(obj)
new Vue({
el: '#app',
data: obj
})
<div id="app">
<p>{{ foo }}</p>
<!-- 这里的 `foo` 不会更新! -->
<button v-on:click="foo = 'baz'">Change it</button>
</div>
Vue实例还暴露了一些有用的实例属性和方法。他们都有前缀$,用于和用户定义的属性区分开来。
var data = { a: 1 }
var vm = new Vue({
el: '#example',
data: data
})
vm.$data === data // => true
vm.$el === document.getElementById('example') // => true
// $watch 是一个实例方法
vm.$watch('a', function (newValue, oldValue) {
// 这个回调将在 `vm.a` 改变后调用
})
实例生命周期钩子
不要在选项属性或回调上使用箭头函数。因为箭头函数并没有
this,this会作为变量一直向上级词法作用域查找,直到找到为止。
不能写:created:()=>{}或vm.$watch('a',()=>{})
生命周期图

模板语法
插值
文本
Mustache语法{{ msg }}
也可配合v-once进行一次性的插值
<span v-once>这个将不会改变: {{ msg }}</span>
原始HTML
v-html指令
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
动态渲染的
html可能会导致xss攻击(跨站脚本),请只对可信内容使用html插值。
Attribute属性
使用v-bind指令
<button v-bind:disabled="isButtonDisabled">Button</button>
如果isButtonDisabled的值是null、undefined、false,那么根本就不会有disabled这个属性。
使用JavaScript表达式
只能使用单个表达式,不能使用语句
指令Directives
参数
v-bind、v-on ...
动态参数
可以用方括号括起来的JavaScript表达式作为一个指令的参数:
<a v-bind:[attributeName]="url"> ... </a>
data中有attributeName这个属性,就会拿到方括号中
动态参数的值的约束
动态参数会预期求出一个字符串,异常情况下值为null。可显式地用于移除绑定。其他非字符串类型都会触发警告。
动态参数表达式的约束
- 不能有空格或者引号
- 最好不要用大写,因为会全部强制转换小写
修饰符
以.指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。
<form v-on:submit.prevent="onSubmit">...</form>
告诉v-on指令对于触发的事件调用event.preventDefault()
缩写
: 和 @
计算属性和侦听器
计算属性
模板内表达式如果放入太多逻辑会难以维护,应该使用计算属性。
<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('')
}
}
})
这里的计算属性reversedMessage将用作属性vm.reversedMessage的getter函数。
console.log(vm.reversedMessage) // => 'olleH'
计算属性缓存VS方法
我们同样可以在表达式中用方法达到一样的效果
<p>Reversed message: "{{ reversedMessage() }}"</p>
// 在组件中
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时,它们才会重新求值。只要message不改变,多次访问reversedMessage就会立即返回原来的结果,而不用再次执行函数。
computed: {
now: function () {
return Date.now()
}
}
所以now是不会改变的,因为Date.now()不依赖任何东西。
而方法就每次都会执行。
为了节省性能。
计算属性vs侦听属性
Vue还有一种更通用的方式观察和响应数据变动:侦听属性。
<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
}
}
})
计算属性的setter
计算属性默认只有getter,不过你也可以自己提供一个
// ...
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 = 'William Duan',setter就会被调用,vm.firstName和vm.lastName也会相应更新。
侦听器
当需要数据变化时执行异步或者开销比较大的操作时,watch就有用了。
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question">
</p>
<p>{{ answer }}</p>
</div>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
},
watch: {
// 如果 `question` 发生改变,这个函数就会运行
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.debouncedGetAnswer()
}
},
created: function () {
// `_.debounce` 是一个通过 Lodash 限制操作频率的函数。
// 在这个例子中,我们希望限制访问 yesno.wtf/api 的频率
// AJAX 请求直到用户输入完毕才会发出。想要了解更多关于
// `_.debounce` 函数 (及其近亲 `_.throttle`) 的知识,
// 请参考:https://lodash.com/docs#debounce
this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
},
methods: {
getAnswer: function () {
if (this.question.indexOf('?') === -1) {
this.answer = 'Questions usually contain a question mark. ;-)'
return
}
this.answer = 'Thinking...'
var vm = this
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = 'Error! Could not reach the API. ' + error
})
}
}
})
watch选项允许我们执行异步操作(访问一个api),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态,这些计算属性做不到。
除了watch还可以使用命令式的vm.$watch API
Class与Style绑定
绑定HTML Class
对象语法
可以传给v-bind:class一个对象,以动态切换class:
<div v-bind:class="{ active: isActive }"></div>
active这个class存在与否,将取决于数据属性isActive的boolean值。
可以将这个对象拿出来,放在data或者计算属性去。
data: {
isActive: true,
error: null
},
computed: {
classObject: function () {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}
数组语法
把数组传给v-bind:class,以应用一个class列表:
<div v-bind:class="[activeClass, errorClass]"></div>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
如果想根据条件切换可以尝试三元表达式:
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
在数组语法中也可以用对象语法
<div v-bind:class="[{ active: isActive }, errorClass]"></div>
用在组件上
在组件上同样适用
绑定内联样式
对象语法
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
activeColor: 'red',
fontSize: 30
}
同样可以拿到data或者用返回对象的计算属性。
数组语法
<div v-bind:style="[baseStyles, overridingStyles]"></div>
自动添加前缀
使用v-bind:style时,会自动添加浏览器引擎前缀。
多重值
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
只会渲染数组中最后一个浏览器支持的值。
条件渲染
v-if
用于条件性地渲染一块内容,只会在指令的表达式返回truthy值的时候被渲染。
也可以用v-else添加一个else块
在template上使用v-if条件渲染分组
因为v-if是一个指令,所以必须将它添加到元素上。要切换多个元素:
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
v-else
v-else必须紧跟在v-if或者v-else-if后面,否则不会被识别。
v-else-if
用key管理可复用的元素
Vue会复用已有元素而不是从头开始渲染。如果你允许用户在不同的登录方式之间切换:
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address">
</template>
上面切换loginType将不会清除用户已经输入的内容,因为两个模板使用了相同的元素,input不会被替换,而仅仅替换了placeholder
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>
这样每次切换输入框都会被重新渲染。
v-show
不同是v-show元素始终会被渲染并保留在DOM中。
v-show只是简单切换元素的css属性display。
v-show不支持template也不支持v-else
对比v-show和v-if
- v-if是真正的条件渲染,会确保切换过程中的销毁和重建。
- v-if是惰性的:如果初始条件为假,则什么也不做,直到条件为真才开始渲染。
- v-show不管什么条件都会渲染。
- 所以,v-if有更高的切换开销,v-show有更高的渲染开销。
不推荐v-if和v-for一起使用
v-for比v-if有更高的优先级。
列表渲染
用v-for把数组对应为一组元素
<ul id="example-1">
<li v-for="(item,index) in items">
{{ item.message }}
</li>
</ul>
v-for还支持可选的第二个参数,index,当前项的索引。
可以用of也可以用in
在v-for里使用对象
<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'
}
}
})
可以提供第二个参数为属性名称(也就是键名)
<div v-for="(value, name) in object">
{{ name }}: {{ value }}
</div>
第三个参数为索引
在遍历对象时,会按照Object.keys()的结果遍历,但是不能保证它的结果在每个js引擎中都一样。
维护状态
为了给Vue提示,以便能跟踪每一个节点,从而重用和重新排序元素,需要给每一项提供一个key属性
<div v-for="item in items" v-bind:key="item.id">
<!-- 内容 -->
</div>
key最好用字符串或者数值类型的值。
数组更新检测
变异方法(mutation method)
Vue将被侦听的数据的变异方法进行了包裹,所以它们也会触发视图更新,包括:
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
替换数组
非变异方法,例如filter(),concat(),slice()。它们不会改变原始数组,而是返回一个新数组,当使用非变异方法时,用新数组替换旧数组。
example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})
Vue为了使得DOM元素得到最大范围的重用,用一个含有相同元素的数组去替换页不会丢弃现有DOM重新渲染整个列表。
注意事项
由于JS的限制,Vue不能检测以下变动:
- 当利用索引直接设置数组项时:vm.items[indexOfItem] = newValue
- 当修改数组长度时:vm.items.length = newLength
要直接设置并且可以出发响应式更新的话可以用:
Vue.set(vm.items,indexOfItem,newValue)
vm.items.splice(indexOfItem,1,newValue)
也可以使用vm.$set实例方法,是全局方法Vue.set的别名:
vm.$set(vm.items,indexOfItem,newValue)
改变长度可以用splice
对象变更检测注意事项
由于JS限制,Vue不能检测对象属性的添加或删除
和上文类似使用set方法
Vue.set(vm.userProfile, 'age', 27)
或者
vm.$set(vm.userProfile, 'age', 27)
如果要给已有对象赋值多个新属性,应该用两个对象的属性创建新的对象:
vm.userProfile = Object.assign({}, vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
显示过滤/排序后的结果
可以创建一个计算属性,返回过滤或排序后的数组
<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
})
}
}
还可以使用方法:
<li v-for="n in even(numbers)">{{ n }}</li>
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}
}
在v-for里使用值范围
<div>
<span v-for="n in 10">{{ n }} </span>
</div>
在template上使用v-for
不推荐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>
在组件上使用v-for
要传数据必须使用prop
<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>
这里的is="todo-item": 在ul中只有li会被看做有效内容。其实就是todo-item组件,但是可以避开一些潜在的浏览器解析错误。
事件处理
监听事件
使用v-on监听DOM事件,并在触发时运行一些js代码
事件处理方法
把这些代码写在methods里面
内联处理器中的方法
也可以在内联js语句中调用
<div id="example-3">
<button v-on:click="say('hi')">Say hi</button>
<button v-on:click="say('what')">Say what</button>
</div>
这种和直接写方法不一样,这种是写语句,直接写方法是传方法的名字过去。
有时候需要在内联语句中访问原始的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()是非常常见的需求。尽管可以在方法中实现,但更好的是用事件修饰符:
- .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>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>
<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>
使用修饰符顺序很重要。v-on:click.prevent.self会阻止所有点击,而v-on:click.self.prevent只会阻止此元素自身的点击。
once修饰符还能用在自定义的组件事件。 还有.passive修饰符
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>
.passive修饰符尤其能够提升移动端的性能
不要把.passive和.prevent一起使用,因为.prevent将会被忽略。.passive会告诉浏览器你不想阻止事件的默认行为。
按键修饰符
在监听键盘事件时,需要知道详细的按键:
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">
可以直接将KeyboardEvent.key暴露的任意有效按键名转换为连字符作为修饰符:
<input v-on:keyup.page-down="onPageDown">
按键码
有一些内置的别名:
.enter.tab.delete.esc.space.up.down.left.right
还可以通过全局config.keyCodes对象自定义别名
Vue.config.keyCodes.f1 = 112
系统修饰符
实现在按下响应按键时触发鼠标或者键盘的监听:
.ctrl.alt.shift.metamac的command
<!-- Alt + C -->
<input @keyup.alt.67="clear">
<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>
必须按住才能触发
.exact修饰符
允许你控制由精准的系统修饰符组合触发的事件
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>
鼠标按钮修饰符
.left.right.middle
为什么要在HTML监听事件
所有Vue事件处理方法和表达式都严格绑定在了当前视图的ViewModel上。
使用v-on的好处
- 扫一眼html模板就能轻松定位js代码里对应的方法。
- 无须在js中手动绑定事件
- 当一个viewmodel被销毁,所有事件处理器都会自动删除。
表单输入绑定
基础用法
使用v-model在input、textarea、select上创建双向数据绑定。
其本质是语法糖。负责监听用户的输入事件以更新数据。
v-model会忽略所有表单元素的value、checked、selected属性的初始值,将Vue实例的数据作为数据来源。应通过js在组件的data中声明初始值。
v-model为不同的输入元素使用不同的属性和不同的抛出事件:
- text和textarea使用value属性和input事件
- checkbox和radio使用checked和change事件
- select将value作为prop并将change作为事件
中文日本等语言打拼音时用v-model不会更新,如要处理可以用input事件。
文本
多行文本
复选框
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>
多个复选框放入一个数组:
<div id='example-3'>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames }}</span>
</div>
new Vue({
el: '#example-3',
data: {
checkedNames: []
}
})
单选按钮
<div id="example-4">
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br>
<span>Picked: {{ picked }}</span>
</div>
new Vue({
el: '#example-4',
data: {
picked: ''
}
})
选择框
<div id="example-5">
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected: {{ selected }}</span>
</div>
new Vue({
el: '...',
data: {
selected: ''
}
})
如果v-model表达式的初始值没有匹配任何选项,select元素就会被渲染为未选中状态。在IOS中就不会触发change事件,建议像上面提供一个值为空的禁用选项。
多选时绑定到一个数组:
<div id="example-6">
<select v-model="selected" multiple style="width: 50px;">
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<br>
<span>Selected: {{ selected }}</span>
</div>
new Vue({
el: '#example-6',
data: {
selected: []
}
})
通常使用用v-for渲染选项。
值绑定
使用v-bind实现绑定value。
选择框的绑定
<select v-model="selected">
<!-- 内联对象字面量 -->
<option v-bind:value="{ number: 123 }">123</option>
</select>
typeof vm.selected // => 'object'
vm.selected.number // => 123
修饰符
.lazy
在默认情况下,v-model在每次input事件触发后将输入框的值与数据进行同步。可以添加lazy修饰符,从而转变为change事件进行同步:
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" >
.number
如果想自动将用户的输入值转为数值类型:
<input v-model.number="age" type="number">
如果这个值无法被parserFloat()解析,就会返回原始值。
.trim
自动过滤用户输入的收尾空白字符:
<input v-model.trim="msg">
在组件上使用v-model
比如一些富文本编辑器。
组件基础
基本示例
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
组件是可复用的Vue示例,且带有一个名字。我们可以在一个通过new Vue创建的Vue根示例中,通过自定义元素使用这个组件。
<div id="components-demo">
<button-counter></button-counter>
</div>
组件的复用
data必须是一个函数
这样每个实例可维护一份被返回对象的独立拷贝:
data: function () {
return {
count: 0
}
}
如果没有这条规则,点击一个按钮就会影响所有其他实例。
组件的组织

可以全局注册也可以局部注册。
全局注册:
Vue.component('my-component-name', {
// ... options ...
})
全局注册的组件可以任何新创建的Vue实例中。
通过prop向子组件传递数据
可以在组件上注册一些自定义的attribute,当一个值传递给这个attribute时候,就变成了那个组件实例的一个属性。
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
<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>
可以使用v-bind动态传递prop。
单个根元素
每个组件必须只有一个根元素。
监听子组件事件
Vue提供了一个自定义事件的系统解决监听子组件事件。父级组件可以通过v-on监听子组件实例的任意事件:
<blog-post
...
v-on:enlarge-text="postFontSize += 0.1"
></blog-post>
同事子组件可以通过调用内建的$emit方法,并传入事件名称来触发一个事件:
<button v-on:click="$emit('enlarge-text')">
Enlarge text
</button>
有了v-on绑定的监听器,父级组件就会接收到该事件并执行js。
使用事件抛出一个值
使用$emit的第二个参数来提供这个值:
<button v-on:click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>
然后当父级组件监听到这个事件的时候,可以通过$event访问到被抛出的这个值。
<blog-post
...
v-on:enlarge-text="postFontSize += $event"
></blog-post>
如果这个事件处理函数的一个方法,那这个值就会作为第一个参数传入这个方法:
methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
在组件上使用v-model
自定义事件也用于创建支持v-model的自定义输入组件。
<input v-model="searchText">
等价于
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
当v-model用在组件上时,就会变成:
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>
而组件内的input必须:
- 将value attribute绑定到一个名叫value的prop上
- 在其input事件被触发时,将新的值通过自定义的input事件抛出
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
通过插槽分发内容
向组件传递内容
<alert-box>
Something bad happened.
</alert-box>
在组件中加入slot
动态组件
在不同组件之间动态切换,通过给component元素加一个特殊的is attribute实现
<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>
通过is属性变成不同组件名来切换
解析DOM模板时的注意事项
对于ul,ol,table,select一些元素,对其内部是什么元素是有严格限制。 这会导致我们使用时出现问题:
<table>
<blog-post-row></blog-post-row>
</table>
可以使用is attribute解决:
<table>
<tr is="blog-post-row"></tr>
</table>