# 组件通信
通常一个单页应用会以一棵嵌套的组件树的形式来组织:
页面首先分成了顶部导航、左侧内容区、右侧边栏 3 部分
左侧内容区又分为上下 2 个组件
右侧边栏中又包含了 3 个子组件
各个组件之间以嵌套的关系组合在一起,那么这个时候不可避免的会有组件间通信的需求。
# 1. 父组件向子组件传递数据
# props
标准的 HTML 元素有属性,例如 <input type="" id="" name="">
,组件也可以有。
子组件的属性,就是使用它的元素(即,其父元素)向它传递数据的途径。
父组件使用子组件时,对子元素的『自定义属性』赋值。子组件的自定义属性名任意,属性值为要传递的数据;
子组件通过 props 接收父组件传递来的值。
在下例中,父组件使用了子组件,并通过子组件的自定义属性 title
,向子组件传递了数据。
<div id="app">
<h1>打个招呼:</h1>
<!-- 使用子组件,同时传递 title 属性 -->
<introduce title="Hello,World"/>
</div>
<script type="text/javascript">
Vue.component("introduce", {
// 直接使用 props 接收到的属性来渲染页面
template:'<h1>{{title}}</h1>',
props:['title'] // 通过 props 来接收一个父组件传递的属性
});
var app = new Vue({
el:"#app"
});
</script>
# props 验证
子元素在自定义属性时,可以提出更多的限定条件,要求父元素在通过这个自定义属性向它传递数据时,必须传递符合某些限定条件的值。
例如,我们定义一个子组件,并接受复杂数据:
const myList = {
// 这个子组件可以对 items 进行迭代,并输出到页面。
template: '\
<ul>\
<li v-for="item in items" v-bind:key="item.id">{{item.id}} : {{item.name}}</li>\
</ul>\
',
props: {
items: { // 属性名为 items
type: Array, // 属性类型为数组
default: [], // 默认值为 []
required: true // 该属性为必须
},
{ ... }, // 其它自定义属性
{ ... }
}
};
当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。
我们在父组件中使用它:
<div id="app">
<h2>课程有:</h2>
<!-- 使用子组件的同时,传递属性,这里使用了v-bind,指向了父组件自己的属性 lessons -->
<my-list v-bind:items="lessons"/>
</div>
var app = new Vue({
el: "#app",
components: {
myList // 当 key 和 value 一样时,可以只写一个
},
data: {
lessons: [
{ id:1, name: 'java' },
{ id:2, name: '测试' },
{ id:3, name: '前端' },
]
}
})
type 类型,可以有:
# | 类型值 |
---|---|
1 | String |
2 | Number |
3 | Boolean |
4 | Array |
5 | Object |
6 | Date |
7 | Function |
8 | Symbol |
# 动态静态传递
给 prop 传入一个静态的值:
<introduce title="Hello,World"/>
给 prop 传入一个动态的值:
通过
v-bind
从父组件的data
中获取值来为子元素的title
属性赋值<introduce v-bind:title="title"/>
另外,有可能这两种传值的写法,看起来会很像,例如:
<son likes="42"></son>
<son v-bind:likes="42"></son>
likes="42"
是静态传值在静态传值中,父元素传递给子元素的属性的值的类型都是『字符串』。这里的 42 实际上是字符串
"42"
,即,子元素的 likes 属性的类型是 String 。v-bind:likes="42"
是动态传值如果是传常量的话,上例并无必要,也不常见,但是语法上是完全正确的。
在这里,传递给子元素的 42 是一个 JavaScript 表达式,它(42)是一个 Number 而非 String 。即,子元素的 likes 属性的类型是 Number 。
# 2. 子组件向父组件传递数据
一个典型的子组件向父组件传递数据的场景是这样的:子组件中有一个按钮,当按钮被点击时,父组件的某个元素的值要发生变化。
想当然的代码可以写成如下形式:
<div id="app">
<h2>num: {{num}}</h2>
<!-- 使用子组件的时候,传递 num 到子组件中 -->
<counter v-bind:num="num"></counter>
</div>
<script type="text/javascript">
Vue.component("counter", { // 子组件,定义了两个按钮,点击数字 num 会加或减
template:'\
<div>\
<button @click="num++">加</button> \
<button @click="num--">减</button> \
</div>',
props:['num'] // count是从父组件获取的。
})
var app = new Vue({
el:"#app",
data:{
num:0
}
})
</script>
子组件接收父组件的 num 属性
子组件定义点击按钮,点击后对 num 进行加或减操作
我们尝试运行,好像没问题,点击按钮试试:
子组件接收到父组件属性后,『默认是不允许修改的』。怎么办?
# 自定义事件
子组件向父组件传递数据要通过『在父组件上自定义事件』来实现。
核心关键点有 2 处:
子组件自定义事件,父组件定义该事件的处理函数。即『提前』做好准备:当发生子组件身上发生 xxx 事件时就执行父组件的 xxx 方法。该方法中去修改父组件的)某个、某些数据。
子组件通过
$emit()
函数发出『通知』:我(子组件)身上发生了 xxx 事件。
最终要变动的是父元素的属性,那么加和减的操作一定是放在父组件:
var app = new Vue({
el: "#app",
data: {
num:0
},
methods: { // 父组件中定义操作 num 的方法
increment() { this.num++; },
decrement() { this.num--; }
}
})
但是,点击按钮是在子组件中,那就是说需要子组件来调用父组件的函数,怎么做?
我们可以『通过 v-on 指令将父组件的函数绑定到子组件的自定义事件』上:
<div id="app">
<h2>num: {{num}}</h2>
<counter v-bind:count="num" v-on:inc="increment" v-on:dec="decrement"></counter>
</div>
当子组件(
<counter>
)上发生 inc 事件时,调用、触发(父组件的)increment()
方法。当子组件(
<counter>
)上发生 dec 事件时,调用、触发(父组件的)decrement()
方法。
简单来说就是,子组件通过 $emit()
上报给父组件自己身上发生了 xxx 事件,而父组件因为提前就已经做好了绑定,当子组件上发生 xxx 事件时,就执行父组件的某某方法,而父组件的某某方法去修改父组件的某某数据。从而实现子组件『间接』修改父组件的数据。
在子组件中定义函数,函数的具体实现调用父组件的实现,并在子组件中调用这些函数。当子组件中按钮被点击时,调用绑定的函数:
Vue.component("counter", {
template:'\
<div>\
<button v-on:click="plus">加</button> \
<button v-on:click="reduce">减</button> \
</div>',
props:['count'],
methods: {
plus() {
this.$emit("inc");
},
reduce(){
this.$emit("dec");
}
}
});
逻辑上,子组件中做了一个『包装』:将 click
事件包装成了自定义的 inc
和 dec
事件。
效果:
# 传递数据
上面的例子中,子组件只需要向父组件『上报』一个提前预定好的自定义事件即可,但是有些情况下,子组件还需要向父组件传递数据。
例如,子组件中是一个 input 输入框,要求其中的输入的数据要传递给父组件,在父组件中展示。
这种情况下,子组件在使用 $emit
方法向父组件上报事件时,需要/可以多传入一个参数,将输入框中的当前值传递给父组件。
子组件核心代码
<input type="text" v-model="msg">
data() { return { msg: '' } }, watch: { msg(newVal, oldVal) { this.$emit('xxx', newVal); } }
父组件核心代码
父组件的 xxx 事件处理函数,就需要多一个参数来接收子组件传递的数据。
<Son @xxx="msgChange"></Son>
methods: { msgChange(msg) { this.msg = msg; } },
『The End』