十,组件
使用组件
注册
可以通过以下这种方式创建一个Vue实例
new Vue({
el: '#some-element',
})
注册一个全局组件,你可以使用Vue.component(tagName,options)
Vue.component('my-component',{ })
在父实例的模块中以自定义元素 <my-component></my-component> 的形式使用
<div id="example">
<my-component></my-component>
</div>
// 注册
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
// 创建根实例
new Vue({
el:'#example'
})
渲染为
<div id="example">
<div>A custom component!</div>
</div>
局部注册
使用组件实例选项注册,可以使组件仅在另一个实例/组件的作用域中可用
var Child = {
template:'<div>A custom component !</div>'
}
new Vue({
components: {
'my-component':Child
}
})
data 必须是函数
使用组件时,大多数选项可以被传入到Vue 构造器中,有一个列外,data 必须是函数。
Vue.component('my-component', {
template: '<span> {{ message}}</span>',
data: {
message: 'hello'
}
})
Vue 会在控制台发出警告,告诉你在组件中 data 必须是一个函数
var data = { counter:0}
Vue.component('simple-counter', {
template: '<button v-on:click="counter += 1">{{ counter }}</button>',
data: function () {
return data
}
})
new Vue({
el: '#example-2'
})
三个组件共享了同一个 data
data: function() {
retrun {
counter: 0
}
}
构成组件
Prop
使用Prop传递数据,组件的作用域是孤立的,意味着不能并且不应该再子组件的
模板内引用父组件的数据,可以使用props 把数据传给子组件。
prop 是父组件用来传递数据的一个自定义属性,子组件需要显示的用props
Vue.component('child', {
// 声明 props
props: ['message'],
// 像 data 一样 prop 可以用在模板内
// 同样也可以在 vm 实例中像 “this.message”这样使用
templae: '<span>{{ message }}</span>'
})
然后向它传入一个普通字符串:
<child message="hello!"></child>
结果:
hello!
camelCase vs.kebab-case
HTML 特性不区分大小写 当使用非字符串模板的时,prop 的名字形式会从camelCase 转为
kebab-case(短横线隔开)
Vue.component('child', {
props:['myMessage'],
template: '<span>{{ myMessage}}</span>'
})
<child my-message="hello"></child>
动态Prop
类似用于v-bind 绑定 HTML 特性到一个表达式,也可以用v-bind 动态绑定props的值
到父组件的数据中。没当父组件的数据变化时,该变化也会传导给子组件。
<div>
<input v-model="paremtMsg">
<child v-bind:my-message="parentMsg"></child>
</div>
使用 v-bind 的缩写语法统称更简单。
<child :my-message="parentMsg"></child>
字面量语法 vs 动态语法
// 实际传递参数
<comp v-bind:some-prop="1"></comp>
单向数据流
1.prop 作为初始值传入,子组件之后只是将它的初始值作为本地数据的初始值使用;
2.prop 作为需要被转变的原始值传入。
定义一个局部的 data 属性 ,并将prop 的初始值作为局部数据的初始值。
props:['initialCounter'],
data: function () {
return { counter: this.initialCounter}
}
定义一个computed 属性。此属性从prop 的值计算出来。
props:['size'],
computed: {
normalizedSize: function() {
return this.size.trim().toLowerCase()
}
}
Prop 验证
组件也可以为props指定验证请求,prop 是一个对象而不是字符串数组时,它包含验证要求:
Vue.component('example', {
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 value > 10
}
}
}
})
自定义事件
子组件需要把数据传回父组件,需要自定义事件。
使用v-on 绑定自定义事件。
使用 $on(eventName) 监听事件
使用 $emit(eventName) 触发事件
给组件绑定原生事件
可以使用 .native 修饰 v-on
<my-component v-on:click.native="doTheThing"></my-component>
使用自定义组件的表单输入组件
自定义事件也可以用来创建自定义的表单输入组件,使用 v-model 来进行数据双向绑定。
<input v-model="something">
非父子组件通信
有时候非父子关系的组件也需要通信,在简单的场景下,使用个空的Vue 实例作为*事件总线。
var bus = new Vue()
// 触发组件 A 中的事件
bus.$emit('id-selected', 1)
// 在组件 B 创建的钩子中监听事件
bus.$on('id-selected', function(id) {
//...
})
使用Slot 分发内容
在使用组件的时候,我们常常遇到这样组合。
<app>
<app-header></app-header>
<app-footer></app-footer>
</app>
注意两点:
1.<app> 组件不知道它的挂载点有什么内容,挂载点的内容是由<app>的父组件决定的
2.组件很有可能有它自己的模板。
分发内容是在父组件作用域内编译。
单个Slot
除非子组件模板至少包含一个<slot>插口, 否则父组件的内容将会被丢弃。当子模板只有
一个没有属性的Slot 时,父组件整个内容片断将插入到slot 所在的DOM位置,并没替换掉
slot 标签本身。
假定 my-component 组件有下面模板:
<div>
<h2>我是子组件的标题</h2>
<slot>
只有在没有要分发的内容时才会显示。
</slot>
</div>
父组件模版:
<div>
<h1>我是父组件的标题</h1>
<my-component>
<p>这是一些初始内容</p>
<p>这是更多的初始内容</p>
</my-component>
</div>
渲染结果:
<div>
<h1>我是父组件的标题</h1>
<div>
<h2>我是子组件的标题</h2>
<p>这是一些初始内容</p>
<p>这是更多的初始内容</p>
</div>
</div>
具名 Slot
<slot> 元素可以用一个特殊的属性 neme 来配置如何分发内容。多个slot 可以有不同的名字,具名solt 将匹配的内容片段中有对应的slot 特殊的元素
仍然可以有个匿名slot,它是默认slot, 作为找不到匹配的内容片断的备用插槽。如果没有默认的slot 这个找不到匹配的内容将会被丢弃。
例如,假定我们有一个 app-layout 组件,它的模板为:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
父组件模版:
<app-layout>
<h1 slot="header">这里可能是一个页面标题</h1>
<p>主要内容的一个段落。</p>
<p>另一个主要段落。</p>
<p slot="footer">这里有一些联系信息</p
</app>
渲染结果为:
<div class="container">
<header>
<h1>这里可能是一个页面标题</h1>
</header>
<main>
<p>主要内容的一个段落。</p>
<p>另一个主要段落。</p>
</main>
<footer>
<p>这里有一些联系信息</p>
</footer>
</div>
动态组件
多个组件可以使用同一个挂载点,然后动态地在它们之间切换。使用保留的 <component> 元素,动态地绑定到它的 is 特性:
var vm = new Vue({
el: '#example',
data: {
currentView: 'home'
},
components: {
home: { /* ... */ },
posts: { /* ... */ },
archive: { /* ... */ }
}
})
<component v-bind:is="currentView">
<!-- 组件在 vm.currentview 变化时改变! -->
</component>
也可以直接绑定到组件对象上:
var Home = {
template: '<p>Welcome home!</p>'
}
var vm = new Vue({
el: '#example',
data: {
currentView: Home
}
})
#keep-alive
如果把切换出来的的组件保留在内存中,可以保留它的状态或避免重新渲染。
<keep-alive>
<component :is="currentView">
// 非活动组件将被缓存
</component>
</keep-alive>
杂项
编写可复用组件
可复用组件应当定义一个清晰的公开接口。
Vue 组件的 API 来自三部分 - props, events 和 slots :
Props 允许外部环境传递数据额给组件。
Events 允许组件触发外部环境的副作用。
Slots 允许外部环境将额外的内容组合在组件中。
使用 v-bind 和 v-on 的简写语法,模板的缩进清楚且简洁:
<my-component
:foo = "baz"
:bar = "qux"
@event-a ="doThis"
@event-b="doThat"
>
<img slot="icon" src="...">
<p slot="main-text"></p>
</my-component>
#子组件索引
可以使用ref 为子组件指定一个索引ID 例如:
<div id="parent">
<user-profile ref="profile"></user-profile>
</div>
var parent = new Vue({
el: '#parent'
})
// 访问子组件
var child = parent.$refs.profile
当 ref 和 v-for 一起使用时, ref 是一个数组或对象,包含相应的子组件