上一篇中谈到了子组件是不能引用父组件或Vue实例挂载的数据。这里Vue实例可看作根组件(root component)。
问题导入
但是在开发中,往往一些数据确实需要从上层传递到下层:
如一个页面中,我们从服务器请求到了很多数据。但这些并非全部应用于一个组件来展示,其中一小部分需要包含的子组件来渲染,(这时我的评价是不如交给子组件去请求需要的数据,各组件请求自己的那份数据不是OK了吗),按照设计者的思路,此时并不会让子组件独自发送一个网络请求的,而是直接让其父组件(大组件)将数据传递给子组件(小组件)。嗯,回过头来想想雀食,一旦下面的子组件过多,所造成的冗余请求对服务器来说,简直是个灾难!
组件间的关系
一般来说,组件可以有以下几种关系:
如上图所示,A 和 B、B 和 C、B 和 D 都是父子关系,C 和 D 是兄弟关系,A 和 C 是隔代关系(可能隔多代)。
如何进行父子组件间的数据通信呢?Vue官方提到:
- 通过props向子组件传递数据。
- 通过$emit向父组件传递数据。
真实开发中,Vue实例与子组件的通信和父组件与子组件的通信过程是一样的。
(注:下面代码示例中,将Vue实例看作父组件,其包含子组件以简化代码)
props基本用法
在组件中,使用选项props来声明需要从父级接收到的数据。
props值的两种写法
方式一:字符串数组,数组中的字符就是传递时的属性名称(attribute)。
方式二:对象,里面可以设置传递时的类型,也可以设置默认值等。
<div id="app">
<cpn :cMovies="movies" :cMessage="message"></cpn>
</div>
<template id="cpn">
<div>
<ul>
<li v-for="(item, index) in cmovies" :key="index">{{item}}</li>
</ul>
<h2>{{cmessage}}</h2>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 父传子:props
const cpn = {
template: "#cpn",
props: ['cmovies', 'cmessage'],// 数组写法,元素虽然是字符串,但可当做变量
data() {
return {
}
},
methods: {
},
};
const app = new Vue({
el: "#app",
data: {
message: "你好",
movies: ["复仇者联盟", "钢铁侠", "星际穿越", "哪吒传奇"]
},
components: {
cpn
}
})
</script>
使用props数组方式接收父组件data数据时,一定要记住v-bind(或:),否则无法解析,会当成字符串输出。
Props的类型
使用props对象方式的话,需要进行类型验证。
数据类型 可以是下列原生构造函数中的一个:
String
Number
Boolean
Array
Object
Date
Function
Symbol
示例:
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
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'].includes(value)
}
}
}
})
prop 对象和数组的默认值为什么要为工厂函数的形式返回?原因在这里
额外的,type
还可以是一个自定义的构造函数,并且通过 instanceof
来进行检查确认。例如,给定下列现成的构造函数:
|
你可以使用:
|
来验证 author
prop 的值是否是通过 new Person
创建的。
Props的驼峰标识
HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:
|
|
如果你使用字符串模板,那么这个限制就不存在了。
示例:
如果props定义的值只是首字母大写(如Cuser),则绑定数据时使用Cuser或C-user均可。
自定义事件
通过自定义事件($emit)可以实现子组件传递数据或事件到父组件中。
主要流程:
- 在子组件中,通过 $emit来触发事件。
- 在父组件中,通过 v-on来监听子组件事件。
这部分直接看示例代码吧,用白话讲有点绕。
<!-- 父组件 -->
<div id="app">
<!-- 不写参数默认传递btnClick的item -->
<cpn @itemclick="cpnClcik"></cpn>
</div>
<!-- 子组件 -->
<template id="cpn">
<div>
<button v-for="(item, index) in categoties" :key="index" @click="btnClick(item)">{{item.name}}</button>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 父传子:props
const cpn = {
template: "#cpn",
data() {
return {
categoties: [{
id: 'aaa',
name: '热门推荐'
},
{
id: 'bbb',
name: '手机数码'
},
{
id: 'ccc',
name: '家用家电'
},
{
id: 'ddd',
name: '电脑办公'
},
]
}
},
methods: {
btnClick(item) {
this.$emit('itemclick', item)
}
},
};
const app = new Vue({
el: "#app",
data() {
return {
}
},
methods: {
cpnClcik(item) {
console.log('cpnClick'+item.name);
}
},
components: {
cpn
},
})
</script>
参考: