组件
什么是组件
组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素
,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 HTML 元素。
所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象(除了一些根级特有的选项)并提供相同的生命周期钩子。
理解组件
简单理解,组件其实就是一个独立的 HTML,它的内部可能有各种结构、样式、逻辑,某些地方来说有些像 iframe,它都是在页面中引入之后展现另一个页面的内容,但实际上它与 iframe 又完全不同,iframe 是一个独立封闭的内容,而组件既是一个独立的内容,还是一个受引入页面控制的内容。
例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。
为什么使用组件
举个简单的列子,最近我的项目中有一个日历模块,多个页面都要用这个日历,而每个页面的日历都存在一些差别,如果不使用组件,我要完成这个项目,做到各个页面的日历大体一致,而部分地方存在差异,我可能就需要写几套日历代码了。
而使用组件呢?一套代码,一个标签,然后分别在不同地方引用,根据不同的需求进行差异控制即可。
<calendar></calendar>
我可以通过给 calendar 传递值实现在本页面对日历的控制,让它满足我这个页面的某些单独需求。
有人会问,你 calendar 标签是什么鬼?前面有这么一句话,组件是自定义元素。calendar 就是我自定义的元素,它就是一个组件。所以在项目中,你会发现有各种五花八门的标签名,他们就是一个个组件。
组件name
在组件中设置name,这样在vue的调试工具中就不会全部显示index组件,而是设置的name,方便找到对应的组件。
<template>
</template>
<script lang="ts">
export default {
name:"组件名称"
}
</script>
组件注册
注册组件
我们把创建一个组件称为注册组件,如果你把组件理解成为变量,那么注册组件你就可以理解为声明变量。我们通过 Vue.component 来注册一个全局组件
// 全局注册
Vue.component(componentName, {
//选项
})
// 局部注册
new Vue({
el:"#app",
components:{
componentName:{
template:`
<div>
<p></p>
<p></p>
<p></p>
</div>
`
}
}
})
对于自定义组件的命名,Vue.js 不强制遵循 W3C 规则(小写,并且包含一个短杠),尽管这被认为是最佳实践。
组件名大小写
定义组件名的方式有两种:
使用 kebab-case
Vue.component('my-component-name', { /* ... */ })
当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 <my-component-name>
。
使用 PascalCase
Vue.component('MyComponentName', { /* ... */ })
当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 <my-component-name>
和 <MyComponentName>
都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。
1- 如果组件名字小写,在视图中小写即可实现渲染
2- 如果组件名字首字母大写,其它小写,那么视图中可以实现渲染(首字母可大写,也可小写)
3- 如果使用驼峰命名,在视图中渲染需要用-进行分割多个单词。
// 建议:
形式一:首字母大写
形式二:全部小写,单词之间用-分割 //(建议 官方写法)
形式三:定义组件采用大驼峰命名,视图中渲染需要全部小写,岮峰之间使用-
组件的选项
- 与创建Vue示例时的选项相同(除了一些根级特有的选项)
一个组件的 data 选项必须是一个函数
(每个组件实例具有自己的作用域,组件复用不会互相影响)
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
组件的使用
<div id="components-demo">
<button-counter></button-counter>
</div>
new Vue({ el: '#components-demo' })
组件复用
你可以将组件进行任意次数的复用:
<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
这里有一个 Vue 组件的示例:
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
组件是可复用的 Vue 实例,且带有一个名字:在这个例子中是 <button-counter>
。我们可以在一个通过 new Vue
创建的 Vue 根实例中,把这个组件作为自定义元素来使用:
<div id="components-demo">
<button-counter></button-counter>
</div>
new Vue({ el: '#components-demo' })
因为组件是可复用的 Vue 实例,所以它们与 new Vue
接收相同的选项,例如 data
、computed
、watch
、methods
以及生命周期钩子等。仅有的例外是像 el
这样根实例特有的选项。
组件data必须是一个函数
当我们定义这个 <button-counter>
组件时,你可能会发现它的 data
并不是像这样直接提供一个对象:
data: {
count: 0
}
取而代之的是,一个组件的 data
选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
// 函数必须返回单例模式的对象,可以让组件中的数据独立
data: function () {
return {
count: 0
}
}
如果 Vue 没有这条规则,点击一个按钮就可能会像如下代码一样影响到其它所有实例:
单个根元素
每个组件必须只有一个根元素
你可以将模板的内容包裹在一个父元素内
<div class="blog-post">
<h3>{{ title }}</h3>
<div v-html="content"></div>
</div>
组件模板
每个组件模板必须只有一个根元素
模板的形式:
template 选项指定字符串 (模板字符串)
javascript// 使用字符串 进行拼接 new Vue({ el:"#app", data:{}, template:"<div>"+"<p>{{a}},{{b}}</p>"+""+"</div>" }) // 或 使用模版字符串 进行拼接 new Vue({ el:"#app", data:{}, template:` <div> <p>{{a}}</p> <p>{{b}}</p> </div> ` })
单文件组件(.vue)
内联模板 (不推荐)
html<my-component inline-template> <div> <p>These are compiled as the component's own template.</p> <p>Not parent's transclusion content.</p> </div> </my-component>
x-templates
Html// 指定id为tp的script标签包裹的内容为模板的内容 <script type="text/x-template" id="hello-world-template"> <p>Hello hello hello</p> </script>
javascript// 全局组件 // Vue.component('hello-world', { template: '#hello-world-template' }) // 局部组件 // 一旦指定了模板,那么会将模板的内容替换掉el所指定的挂载元素。 new Vue({ el:"#app", data:{ a:1, b:2 }, methods:{}, filters:{}, computed:{}, // 指定id为tp的script标签包裹的内容为模板的内容 template:"#hello-world-template" })
template标签
javascript<template id="hello-world-template"> <p>Hello hello hello</p> </template>
javascript// 全局组件 Vue.component('hello-world', { template: '#hello-world-template' }) // 局部组件 // 一旦指定了模板,那么会将模板的内容替换掉el所指定的挂载元素。 new Vue({ el:"#app", data:{ a:1, b:2 }, methods:{}, filters:{}, computed:{}, // 指定id为tp的script标签包裹的内容为模板的内容 template:"#hello-world-template" })
全局组件
使用 Vue.component()
注册的组件都是全局组件
这些组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue
) 的模板中。比如:
全局组件的注册要写在Vue实例化的前边
Vue.component('my-component-name', {
// ... options ...
})
Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })
new Vue({ el: '#app' })
<div id="app">
<component-a></component-a>
<component-b></component-b>
<component-c></component-c>
</div>
在所有子组件中也是如此,也就是说这三个组件在各自内部
也都可以相互使用。
全局对象的推荐写法
// index.html
import components from "./components/index.js";
new Vue({
})
// index.js
import One from "./One.js";
import Two from "./Two.js";
// 暴露出去的是一个对象,对象中的属性即是全局组件的配置信息,属性的名字即是组件的名字。
export default {
One,
Two
}
局部组件
全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。
在这些情况下,你可以通过一个普通的 JavaScript 对象来定义组件:
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = {
template: `
<div>
<h3>Hanser</h3>
</div>
`
}
然后在 components
选项中定义你想要使用的组件:
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
对于 components
对象中的每个 property 来说,其 property 名就是自定义元素的名字,其 property 值就是这个组件的选项对象。
注意局部注册的组件在其子组件中不可用。例如,如果你希望 ComponentA
在 ComponentB
中可用,则你需要这样写:
var ComponentA = { /* ... */ }
// 局部组件的嵌套使用
var ComponentB = {
components: {
'component-a': ComponentA
},
// ...
}
或者如果你通过 Babel 和 webpack 使用 ES2015 模块,那么代码看起来更像:
import ComponentA from './ComponentA.vue'
export default {
components: {
ComponentA
},
// ...
}
在对象中放一个类似 ComponentA 的变量名其实是 ComponentA: ComponentA 的缩写
模块系统
推荐创建一个 components 目录,并将每个组件放置在其各自的文件中。
然后你需要在局部注册之前导入每个你想使用的组件。例如,在一个假设的 ComponentB.js
或 ComponentB.vue
文件中:
import ComponentA from './ComponentA.js'
import ComponentC from './ComponentC.js'
export default {
components: {
ComponentA,
ComponentC
},
// ...
}
// ComponentA.js
import PageList from "./PageList.js";
// 评论区域模版
const template = (`
<div class="takeComment">
<textarea name="textarea" class="takeTextField" id="tijiaoText"></textarea>
<div class="takeSbmComment">
<input type="button" class="inputs" value="" />
</div>
</div>
`)
// 组件的嵌套
const components = {
PageList
}
export default{
template,
components
}
现在 ComponentA
和 ComponentC
都可以在 ComponentB
的模板中使用了。
基础组件的自动化全局注册
可能你的许多组件只是包裹了一个输入框或按钮之类的元素,是相对通用的。我们有时候会把它们称为基础组件,它们会在各个组件中被频繁的用到。
所以会导致很多组件里都会有一个包含基础组件的长列表:
import BaseButton from './BaseButton.vue'
import BaseIcon from './BaseIcon.vue'
import BaseInput from './BaseInput.vue'
export default {
components: {
BaseButton,
BaseIcon,
BaseInput
}
}
而只是用于模板中的一小部分:
<BaseInput
v-model="searchText"
@keydown.enter="search"
/>
<BaseButton @click="search">
<BaseIcon name="search"/>
</BaseButton>
如果你恰好使用了 webpack (或在内部使用了 webpack 的 Vue CLI 3+),那么就可以使用 require.context
只全局注册这些非常通用的基础组件。这里有一份可以让你在应用入口文件 (比如 src/main.js
) 中全局导入基础组件的示例代码:
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
const requireComponent = require.context(
// 其组件目录的相对路径
'./components',
// 是否查询其子目录
false,
// 匹配基础组件文件名的正则表达式
/Base[A-Z]\w+\.(vue|js)$/
)
requireComponent.keys().forEach(fileName => {
// 获取组件配置
const componentConfig = requireComponent(fileName)
// 获取组件的 PascalCase 命名
const componentName = upperFirst(
camelCase(
// 获取和目录深度无关的文件名
fileName
.split('/')
.pop()
.replace(/\.\w+$/, '')
)
)
// 全局注册组件
Vue.component(
componentName,
// 如果这个组件选项是通过 `export default` 导出的,
// 那么就会优先使用 `.default`,
// 否则回退到使用模块的根。
componentConfig.default || componentConfig
)
})
记住全局注册的行为必须在根 Vue 实例 (通过 new Vue
) 创建之前发生。
Prop
组件设计初衷就是要配合使用的,最常见的就是形成父子组件的关系:组件 A 在它的模板中使用了组件 B。它们之间必然需要相互通信:父组件可能要给子组件下发数据,子组件则可能要将它内部发生的事情告知父组件。
每个组件的作用域都是独立的,所以在组件嵌套使用的时候子组件不能直接使用父组件中的数据。
Vue实例与组件实例
给Vue.prototype设置属性,一般自定义属性前边加$
Vue.prototype.$useName = "zhangsan";
组件中的vc(VueComponent函数返回的)对象在Vue.prototype的原型链上
Vue.prototype对象中有什么属性,那么组件的实例vc就拥有什么属性
vc也是通过New Vue生成的
所以vc.__proto__.__proto__ 不等于 Vue.prototype
vm.__proto__ == Vue.prototype
// 给Vue.prototype设置属性useName
Vue.prototype.$useName = "zhangsan";
// 创建Vue实例vm
const vm = new Vue({
el:'#app',
methods:{
fun(){
console.log(this === vm);// true this是通过Vue构造函数 实例化出来的 vm
console.log(this.__proto__ === Vue.prototype);// true
console.log(vm.__proto__ === Vue.prototype);// true
console.log(this.$useName) // zhangsan
console.log(this.__proto__.$useName) // zhangsan
},
},
components:{
// Hanser组件
Hanser:{
// Vue.prototype对象中有什么属性,那么组件的实例vc就拥有什么属性
// 组件中的vc实例在Vue.prototype的原型链上
template:(`<button @click="fun2">Click Hanser</button>`),
methods:{
fun2(){
// this是通过VueComponent函数 创建出来的 vc
console.log(this);
console.log(this.__proto__); //Vue{}的对象
// false 组件中vc.__proto__与 vm 不是同一个对象
console.log(this.__proto__ === vm);
//true => 组件中的实例在Vue.prototype的原型链上
console.log(this.__proto__.__proto__ === Vue.prototype);
console.log(this.$useName) // zhangsan
console.log(this.__proto__.__proto__.$useName) // zhangsan
},
},
},
}
通过Prop向子组件传递数据
父向子组件传递数据通过属性传值 子组件设置props选项,父元素通过给子组件的props['属性名']中的属性名传值。
子组件不可以直接使用传递过来的属性,需要对其通过props进行设置
一旦子元素在props中指定接收的属性名后,那么函数,过滤器等不允许与设置的属性名相同
Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property。为了给博文组件传递一个标题,我们可以用一个 props
选项将其包含在该组件可接受的 prop 列表中:
静态传值
在子组件中声明 prop,然后添加一个 message
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。
我们能够在组件实例中访问这个值,然后直接传入值就可以在子组件中使用 message,就像访问 data
中的值一样
一个 prop 被注册之后,就可以像这样把数据作为一个自定义 attribute 传递进来:
<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>
动态传值
prop 可以通过 v-bind
动态赋值,例如
<!-- 动态赋予一个变量的值 -->
<blog-post v-bind:title="post.title"></blog-post>
<!-- 动态赋予一个复杂表达式的值 -->
<blog-post
v-bind:title="post.title + ' by ' + post.author.name"
></blog-post>
<template>
<Child :userName="userName" :age="age" hobby="1"/>
<!-- 上面使用子组件的语法糖,效果相同,一种简写 -->
<Child v-bind="{userName,age,hobby:'1'}" />
</template>
<script>
import Child from "@/components/Child";
export default {
name: "App",
components: {Child},
data(){
return {
userName:"zhangsan",
age:10
}
}
}
</script>
在HTML中这样定义
<div id="app">
<span>aqua的体重是:{{aquaWeight}}</span>
<!-- 子组件中的属性名 = "父实例中的data属性名" -->
<hanser
:hanser-weight="hanserWeight1"
:hanserpassword="hanserPassword1"
hanser-age = "12"
:hanser-sex = "hanserSex"
></hanser>
</div>
组件中
// 全局组件
const vc = Vue.component("hanser",{
// 自定义子组件标签中的属性名(JS中使用小驼峰,HTML中使用小写- )
props:['hanserWeight','hanserpassword','hanserAge','hanserSex'],
// hanser标签模版
// 模版中使用属性名就相当于使用父实例的data属性
template:(`
<div>
<span>Hanser的体重是:{{hanserWeight}}</span> <br>
<span>Hanser的密码是:{{hanserpassword}}</span> <br>
<span>Hanser的年龄是:{{hanserAge}}</span> <br>
<span>Hanser的性别是:{{hanserSex}}</span> <br>
</div>
`),
methods:{
fun2(){
},
},
})
Vue实例中
// 创建Vue实例vm
const vm = new Vue({
el:'#app',
data:{
aquaWeight:44.5,
hanserWeight1:47.4,
hanserPassword1:"123123",
hanserSex:"女"
},
methods:{
fun(){
},
},
})
在一个典型的应用中,你可能在 data
里有一个博文的数组
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' }
]
}
})
并想要为每篇博文渲染一个组件:
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
></blog-post>
传入各种类型的数据
在上述两个示例中,我们传入的值都是字符串类型的,但实际上任何类型的值都可以传给一个 prop。
传入一个数字
传入一个布尔值
传入一个数组
传入一个对象
注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。
Prop 的大小写
HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM (JS)中的模板时,camelCase (驼峰命名法) 的 prop 名时,视图上(HTML)的属性需要使用其等价的 kebab-case (短横线分隔命名) 命名
传入一个对象的所有属性
<blog-post v-bind="post"></blog-post>
等价于
<blog-post
v-bind:id="post.id"
v-bind:title="post.title"
></blog-post>
看起来当组件变得越来越复杂的时候,我们的博文不只需要标题和内容,还需要发布日期、评论等等。为每个相关的信息定义一个 prop 会变得很麻烦:
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
v-bind:content="post.content"
v-bind:publishedAt="post.publishedAt"
v-bind:comments="post.comments"
></blog-post>
重构一下这个 <blog-post>
组件了,让它变成接受一个单独的 post
prop:
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
></blog-post>
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<div v-html="post.content"></div>
</div>
`
})
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' }
]
}
})
把post作为一个对象放入props选项中
prop初始值
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。子组件不能直接修改传来的值(引入的方式))
这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。在这种情况下,最好定义一个本地的 data property 并将这个 prop 用作其初始值:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。
Prop验证
我们可以为组件的 prop 指定验证要求
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 匹配任何类型)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组且一定会从一个工厂函数返回默认值 //
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})
当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。
注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的 property (如
data
、computed
等) 在default
或validator
函数中是不可用的。
类型列表:
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
- 自定义的构造函数
额外的,type
还可以是一个自定义的构造函数,并且通过 instanceof
来进行检查确认。例如,给定下列现成的构造函数:
function Person (firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
你可以使用:
Vue.component('blog-post', {
props: {
author: Person
}
})
来验证 author
prop 的值是否是通过 new Person
创建的。
非 Prop 的 Attribute
详见 Vue官网
父向子传值
通过父向子组件传递一个方法函数,然后子组件调用这个函数达到视图改变
<div id="app">
<button @click="changeIsShow(!isShow)">显示与隐藏</button>
<app v-show="isShow" :change-is-show="changeIsShow"></app>
</div>
new Vue({
el:"#app",
data:{
isShow:true
},
methods:{
changeIsShow(isShow){
this.isShow = isShow;
}
},
components:{
App:{
props:{
// props验证
changeIsShow:Function
},
data(){
return {
bol:true
}
},
template:(`
<div style="width:600px;height:300px;background:skyblue;">
<button @click="changeIsShow(false)">关闭</button>
</div>
`)
}
}
})
插槽 slot
通过插槽分发内容
Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})
<alert-box>
Something bad happened.
</alert-box>
·Something bad happened.· 会替换掉 slot标签
模板中多个插槽
组件模板
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
调用组件
<base-layout>
<template slot="header">
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<p slot="footer">Here's some contact info</p>
</base-layout>
插槽默认内容
<button type="submit">
<slot>Submit</slot>
</button>
动态组件&异步组件
实现动态组件
在不同组件之间进行动态切换 Vue提供的内置组件component,
参数is的表达式是**"组件名称","组件名称"**是谁,这个组件component就代表谁。
<component is="组件名" class="tab"></component>
// 绑定实例的属性
<component :is="arr[index]" class="tab"></component>
new Vue({
data:{
arr:['12','23']
index:1
}
})
实现选项卡案例
在动态组件上使用 keep-alive
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们
主要用于保留组件状态或避免重新渲染
<!-- 基本 -->
<keep-alive>
<component :is="view"></component>
</keep-alive>
<!-- 多个条件判断的子组件 -->
<keep-alive>
<comp-a v-if="a > 1"></comp-a>
<comp-b v-else></comp-b>
</keep-alive>
绑定组件选项对象
动态组件可以绑定 组件选项对象(有component属性的对象),而不是已注册组件名的实例
var tabs = [
{
name: 'Home',
component: {
template: '<div>Home component</div>'
}
},
{
name: 'Posts',
component: {
template: '<div>Posts component</div>'
}
},
{
name: 'Archive',
component: {
template: '<div>Archive component</div>',
}
}
]
new Vue({
el: '#dynamic-component-demo',
data: {
tabs: tabs,
currentTab: tabs[0]
}
})
<component
v-bind:is="currentTab.component"
class="tab"
>
</component>
处理边界情况
组件的其他特性
解析 DOM 模板时的注意事项
有些 HTML 元素,诸如 <ul>
、<ol>
、<table>
和 <select>
,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li>
、<tr>
和 <option>
,只能出现在其它某些特定的元素内部。
<table>
<blog-post-row></blog-post-row>
</table>
上面的写法,渲染效果会不甚理想,可以采用以下写法
<table>
<tr is="blog-post-row"></tr>
</table>
需要注意的是如果我们从以下来源使用模板的话,这条限制是不存在的:
- 字符串 (例如:template: '...')
- 单文件组件 (.vue)
<script type="text/x-template">
Prop的一些问题
Prop的属性名问题
HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名
如果你使用字符串模板,那么这个限制就不存在了。
非Prop属性
组件上定义的非Prop属性 会传递到 组件模板的根元素上
class 和 style 特性会非常智能,即两边的值会被合并起来
对prop重新赋值
子组件中,对prop重新赋值,会报警告
组件事件的相关问题
将原生事件绑定到组件
想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on 的 .native 修饰符
<base-input v-on:focus.native="onFocus"></base-input>
.sync 修饰符
在有些情况下,我们可能需要对一个 prop 进行“双向绑定”
推荐以 update:my-prop-name 的模式触发事件
//子组件中
this.$emit('update:title', newTitle)
<!-- 上级组件 模板中 -->
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
以上写法可以换成下列写法
<text-document v-bind:title.sync="doc.title"></text-document>