组件
-
组件是可复用的 Vue 实例,且带有一个名字
-
每用一次组件,就会有一个它的新实例被创建
-
data 必须是一个函数
data: function () { return { count: 0 } }
1. 注册组件
-
全局注册
可以用在任何新创建的 Vue 根实例的模板中。第一个参数是组件名
Vue.component('my-component-name', { // ... options ... }) -
局部注册
即在哪注册在哪用,这样就不会过多的占用内存,首先注册一个局部组件
var ComponentA={ props:['title'], template:`<h3>{{title}}</h3>` }然后实例的
components对象中使用,属性名就是组件名,属性值就是你想传入的组件new Vue({ el: '#app', components:{ 'blog-title':ComponentA } })
注意:推荐使用kebab-case(短横线分割命名)给组件命名,例如该例子中的
<blog-title>,否则容易报错:
注意:每个组件必须只有一个根元素,即
template里只能有一个根标签
2. Prop
#prop大小写
prop对大小写不敏感,所以可以这样使用:
Vue.component('blog', {
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
<blog post-title="hello!"></blog>
#prop类型
一般情况下是以字符串数组形式列出的 prop:
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
也可以给每个prop指定值类型。属性名是prop名称,属性值是prop类型
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
//也可以是Data、Symbol
#传递prop
一个例子
Vue.component('blog-post', {
props: ['title'], //给组件定义一个属性
template: '<h3>{{ title }}</h3>'
})
<blog-post title="My journey with Vue"></blog-post> //传值
-
静态传递
# 传入字符串 <blog-post title="My journey with Vue"></blog-post> #有时候需要用v-bind告诉vue这是一个js表达式而不是字符串 # 传入布尔值, <blog-post :is-published="false"></blog-post> # 传入数组 <blog-post :comment-ids="[234, 266, 273]"></blog-post> # 传入对象 <blog-post :author="{name: 'Veronica',company: 'Veridian Dynamics'}"></blog-post> # 传入一个对象所有的属性 -
动态传递
new Vue({ el: '#blog', 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" :key="post.id" :title="post.title" ></blog-post>当需要传递的属性很多时,可以将这些属性成合成一个。在这个例子中,可以把所有
post所有的属性如title、content写在一个post中:
-
重构组件
原本子组件
Vue.component('blog-post', { props: ['title','content'], template: ` <div> <h3>{{ title }}</h3> <div v-html="content"></div> </div> ` })现在子组件
Vue.component('blog-post', { props: ['post'], template: ` <div> <h3>{{ post.title }}</h3> <div v-html="post.content"></div> </div> ` }) -
创建实例
new Vue({ el: '#blog', data: { posts: [ { id: 1, title: 'My journey with Vue',content:'111' }, { id: 2, title: 'Blogging with Vue',content:'222' }, { id: 3, title: 'Why Vue is so fun',content:'333'} ] } }) -
在实例中使用组件
原本父级
<blog-post v-for="post in posts" :key="post.id" :title="post.title" :content="post.comtent"></blog-post>现在父级
<blog-post v-for="post in posts" :key="post.id" :post="post"></blog-post>
#单项数据流
父级 prop 的更新会向下流动到子组件中,但是反过来则不行
#prop验证
可以为组件的 prop 指定验证要求。
- 当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。
- prop 会在一个组件实例创建之前进行验证
Vue.component('my-component', {
props: {
//1. 基础的类型检查 (null和undefined会通过任何类型验证)
propA: Number,
//2. 多个可能的类型
propB: [String, Number],
//3. 必填的字符串
propC: {
type: String,
required: true
},
//4. 带有默认值的数字
propD: {
type: Number,
default: 100
},
//5.自定义验证函数。下面例子表示必须匹配下列字符串中的一个
propF: {
validator: function (value) {
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})
3. 自定义事件
#事件名
推荐始终使用 kebab-case 的事件名
this.$emit('my-event')
<my-component @my-event="doSomething"></my-component>
#监听子组件事件
-
创建实例
new Vue({ el: '#demo', data: { posts: [/* ... */], postFontSize: 1 } }) -
用
$emit给子组件添加事件Vue.component('blog-post', { props: ['post'], template: ` //... <button @click="$emit('enlarge-text')">Enlarge</button> //... ` }) -
v-on监听子组件事件。当子组件触发时,开始响应<div id="demo"> <div :style="{ fontSize: postFontSize + 'em' }"> <blog-post ... @enlarge-text="postFontSize += 0.1" ></blog-post> </div> </div>
$emit可以接收第二个参数
-
给子组件事件添加参数
<button v-on:click="$emit('enlarge-text', 0.1)">Enlarge</button> -
父级通过
$event获取整个参数值<blog-post ... @enlarge-text="postFontSize += $event" ></blog-post>第二种写法:
<blog-post ... @enlarge-text="enlargeText" ></blog-post>传过来的参数值会作为该方法的第一个参数
methods: { enlargeText: function (num) { this.postFontSize += num } }
#自定义事件中的v-model
-
普通情况下自定义事件
<input v-model="searchText">等价于
<input :value="searchText" @input="searchText = $event.target.value"> -
在组件上自定义事件
<custom-input v-model="searchText"/>等价于
<custom-input :value="searchText" @input="searchText = $event"/>这时候需要更改子组件,才能让其正常运作。
Vue.component('custom-input', { props: ['value'], template: ` <input :value="value" @input="$emit('input', $event.target.value)" > ` })解析:这时子组件的
input触发时,会抛出$event.target.value,父级会监听到并接收$event.target.value。此时父级的$event实际上就等于子组件抛出的$event.target.value,变化后的searchText的值会绑定在父级的value上,然后通过props传回子组件,子组件接收到这个props值后就绑定在input的value上,这时候就在页面呈现出效果了
#绑定原生事件
使用 v-on 的 .native 修饰符:
<base-input @focus.native="onFocus"></base-input>
注意:
.native监听的是组件的根元素。如果想监听到子元素,需要用到$listeners对象
4. 插槽
#一个例子
-
在子组件模板中加上一个插槽
<slot></slot>Vue.component('alert-box', { template: ` <div> <strong>Error!</strong> <slot></slot> </div> ` }) -
在父级组件内添加内容后,内容就会自动放进插槽内
<alert-box>Something bad happened.</alert-box>
注意:如果子的
template中没有包含一个slot元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃
#默认内容
默认内容会在没有提供内容的时候被渲染。只需要将内容放在slot中:
//在一个子组件的template中
<button>
<slot>Submit</slot>
</button>
//1. 父级中没有添加任何内容
<submit-button></submit-button>
// 渲染后备内容:
<button>Submit</button>
//2. 父级中有内容
<submit-button>Save</submit-button>
// 后备内容会被取代:
<button>Save</button>
#具名插槽
当我们需要多个插槽时,具名插槽很有用:
-
组件模板中,给
slot添加name属性//假设这是base-layout组件的模板,需要分别给header、main、footer添加插槽: <div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> //这个插槽没有名字,name的值默认为default </main> <footer> <slot name="footer"></slot> </footer> </div> -
父级中,用带有
v-slot的template包裹内容,v-slot指定插槽名称<base-layout> <template v-slot:header> <h1>I am a header</h1> </template> //任何没有按规定包裹的内容都会被视为默认插槽的内容 <p>A paragraph for the main content.</p> <p>And another one.</p> <template v-slot:footer> <p>I am a footer</p> </template> </base-layout>v-slot可以缩写:<template #header> <h1>I am a header</h1> </template>
#编译作用域
插槽的作用域与实例相同。即能访问到实例的数据,而不能访问组件的数据
//可以访问
<navigation-link url="/profile">{{ user.name }}</navigation-link>
//不可以访问
<navigation-link url="/profile">{{ url }}</navigation-link>
#插槽作用域
如果想在父级插槽内容中访问到子组件的数据
-
在子组件的
slot中绑定数据Vue.component('user-info', { //user作为属性绑定数据。在这里user属性被称为插槽prop template: `<span><slot :user="userdata">{{userdata.current}}</slot></span>`, data:()=>({ userdata:{ before:'lihua', current:'xiaoming' } }), }) -
用
v-slot来定义插槽 prop 的名字。名字也可以是其他<user-info v-slot:default="slotProps"> {{slotProps.user.before}} </user-info>由于指定的插槽是default,所以以上也可以简写成:注意不要与具名插槽混淆
<user-info v-slot="slotProps"> {{slotProps.user.before}} </user-info>
自 2.6.0 起有所更新。已废弃的使用
slot-scope。
# 动态插槽名
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
5. 动态组件
如果在一个多标签界面实现组件切换,可以通过 Vue 的 <component> 元素加一个特殊的 is attribute 来实现::
<!-- 切换tab值 -->
<button v-for="tab in tabs" :key="tab" @click="currentTab = tab">{{tab}}</button>
<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>
Vue.component("com1", {
template: "<div>com1</div>"
});
Vue.component("com2", {
template: "<div>com2</div>"
});
Vue.component("com3", {
template: "<div>com3</div>"
});
new Vue({
el: '#app',
data:{
currentTab:"com1",
tabs:["com1", "com2", "com3"]
},
computed: {
//currentTabComponent可以是已注册组件的名字,或一个组件的选项对象
currentTabComponent: function () {
return this.currentTab
}
}
})
