组件
组件(Component)是Vue.js最核心的功能,也是整个框架设计最精彩的地方,当然也是最难掌握的。组件可以扩展HTML元素,封装可重用的代码。为什么使用组件?当然是为了提高代码的重用性;
组件的用法
组件是需要注册才可以使用的,注册有全局注册和局部注册两种方式。全局注册后,任何Vue实例都可以使用,
- 全局注册代码如下:
Vue.component('my-component', {
// 选项
})
// my-component就是注册的组件自定义标签名称,推荐使用小写或加减号分割的形式命名。要在父实例中使用这个组件,必须要在实例创建前注册,之后就可以用<my-component></my-component>的形式来使用组件了。
<div id="app">
<my-component></my-component>
</div>
<script>
Vue.component('my-component', {
//选项
});
var app = new Vue({
el: '#app'
})
</script>
此时的页面还是空白的,因为我们注册的组件没有任何内容,在组件选项中添加template就可以显示内容了。此时注意template的DOM结构必须被一个元素包含。
Vue.component('my-component', {
template: '<div>这里是组件的内容</div>'
});
- 局部注册
局部注册的组件只有在该实例作用域下有效。组件中也可以使用components选项来注册,使组件可以嵌套,示例如下:
<div id="app">
<my-component></my-component>
</div>
<script>
var child = {
template: '<div>局部注册组件的内容</div>'
}
var app = new Vue({
el: '#app',
components: {
'my-component': child
}
})
</script>
Vue组件的模板在某些情况下会受到HTML的限制,比如table标签内只允许使用tr、th等表格元素,所以在table内直接使用组件是无效的。在这种情况下,可以使用特殊的is属性来挂载组件,示例如下:
<table>
<tbody is="my-component"></tbody>
</table>
tbody在渲染是会被替换为组件的内容,常用的限制元素如 ul、select等同理。
父组件向子组件传值
子组件通过props来声明需要从父组件接收的数据,props的值可以有两种,一种是字符串数组,一种是对象。
1. 字符串数组用法
<div id="app">
<my-component message="来自父组件的数据"></my-component>
</div>
<script>
Vue.component('my-component', {
props: ['message'],
template: '<div>{{ message }}</div>'
});
var app = new Vue({
el: '#app'
})
</script>
渲染后的结果为
<div id="app">
<div>来自父组件的数据</div>
</div>
props中声明的数据与组件中data函数return的数据主要区别是:props的来自父级,而data中的是组件自己的数据,作用域是组件本身,这两种数据都可以在模板template及计算属性computed和方法methods中使用。
在props中声明过的属性,不需要在data中重复声明,可直接使用。
由于HTML特性不区分大小写,当使用DOM模板时,驼峰命名(camelCase)的props名称要转为短横分隔命名(kebab-case);
<div id="app">
<my-component warning-text="提示信息"></my-component>
</div>
<script>
Vue.component('my-component', {
props: ['warningText'],
template: '<div>{{ message }}</div>'
});
var app = new Vue({
el: '#app'
})
</script>
如果来自父级的数据并不是直接写死的,而是动态数据,这时可以使用指令v-bind来动态绑定props的值,当父组件的数据变化时,也会传递给子组件。
<div id="app">
<input type="text" v-model="parentMsg">
<my-component :message="parentMsg"></my-component>
</div>
<script>
Vue.component('my-component', {
props: ['message'],
template: '<div>{{ message }}</div>'
});
var app = new Vue({
el: '#app',
data() {
return {
parentMsg: ''
}
}
})
</script>
- 单向数据流
为了尽可能将父子组件解耦,避免子组件无意中修改了父组件的状态,Vue 2.x 之后通过props父组件向子组件传递数据是单向的。但有两种可改变prop的情况;
1. 子组件中重新声明变量,在组件初始化时会获取来自父组件的数据,之后只用修改子组件的变量。
<div id="app">
<my-component :init-count="1"></my-component>
</div>
<script>
Vue.component('my-component', {
props: ['initCount'],
template: '<div>{{ count }}</div>',
data() {
return {
count: this.initCount
}
}
});
var app = new Vue({
el: '#app'
})
</script>
2. prop作为需要被转变的原始值传入时,可使用计算属性
<div id="app">
<my-component :width="100"></my-component>
</div>
<script>
Vue.component('my-component', {
props: ['width'],
template: '<div :style="style">组件内容</div>',
computed: {
style() {
return {
width: this.width + 'px'
}
}
}
});
var app = new Vue({
el: '#app'
})
</script>
注意: 在JavaScript中,对象和数据是引用类型,指向同一个内存空间,所以props是对象和数组时,在子组件内改变是会影响父组件的。
- prop的对象用法 当prop需要验证时,就需要对象写法;
Vue.component('my-component', {
props: {
propA: {
type: [String, Number], // 必须是字符串或数字类型
default: '', // 默认值为空字符串
required: true, // 必传
validator(value) { // 自定义一个验证函数
return value > 10;
}
}
}
});
验证的type类型可以是String、Number、Boolean、Object、Array、Function
组件通信
父子组件通信
我们已经知道父组件向子组件通信,通过props传递数据子组件用$emit来触发事件,那父子组件、兄弟组件、跨级组件又是如何通讯的呢?请接着往下看:
<div id="app">
<p>总数:{{ total }}</p>
<my-component @increse="handleIncrese"></my-component>
</div>
<script>
Vue.component('my-component', {
props: ['width'],
template: '\
<div>\
<Button @click="increse">+1</Button>\
<div>',
data() {
return {
counter: 0
}
},
methods: {
increse() {
this.counter++;
this.$emit('increse', this.counter);
}
}
});
var app = new Vue({
el: '#app',
data() {
return {
total: 0
}
},
methods: {
handleIncrese(count) {
this.total = count;
}
}
})
</script>
上面的示例中,在改变组件的data counter后,通过emit()方法的第一个参数是自定义事件名称,后面的参数是要传递的数据,可以不填或填多个。
非父子组件通信
Vue.js 2.x中,推荐使用空的vue实例作为中央事件总线(bus),
先看下下面的示例,试着运行下,会有什么样的结果呢?
<div id="app">
<my-component :msg-a="otherMsg"></my-component>
<other-component :msg-b="message"></other-component>
</div>
<script>
var bus = new Vue();
Vue.component('my-component', {
props: ['msgA'],
template: '\
<div>\
<p>A: {{msgA}}</p>\
<Button @click="handleEvent">A兄弟组件</Button>\
<div>',
methods: {
handleEvent() {
bus.$emit('on-message', '来自A组件的内容');
}
}
});
Vue.component('other-component', {
props: ['msgB'],
template: '\
<div>\
<p> B:{{ msgB }}</p>\
<Button @click="handleEvent1">B兄度组件</Button>\
<div>',
methods: {
handleEvent1() {
bus.$emit('other-message', '来自B组件内容');
}
}
});
var app = new Vue({
el: '#app',
data() {
return {
message: '',
otherMsg: ''
}
},
mounted() {
// 在实例初始化时监听来自bus的事件
bus.$on('on-message', (msg) => {
this.message = msg;
});
bus.$on('other-message', (msg) => {
this.otherMsg = msg;
});
}
})
</script>
示例中首先创建了一个名为bus的空Vue实例,里面没有任何内容,然后全局定义了组件components,在app实例化时,在生命周期mounted钩子函数里监听来自bus的事件,可将组件A的内容传至组件B,组件B的内容传至组件A,从而巧妙的实现了任何组件间的通信。包括:父子、兄弟、跨级组件之间。
子组件索引
Vue提供了特殊的属性ref来为子组件指定一个索引名称,在父组件内通过this.$refs来访问指定名称的子组件。
<child-component ref="child"></child-component>
refs。
大家可以试着补全下面的代码,分别打印胡两个ref的内容看看会是什么?
<div id="app">
<p ref="p"></p>
<child-component ref="child></child-component>
</div>
网友评论