组件的嵌套
在Vue的设计中,一切都可以看作组件。整个页面可以看作是一个根组件,内部的各块组件可以看作子组件。组件之间自然的会发生嵌套关系。
接下来,我们看一下组件嵌套的示例:
<div id="app">
<my-table></my-table>
</div>
<template id="table-head">
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>age</th>
<th>gender</th>
</tr>
</thead>
</template>
<template id="table-body">
<tbody>
<tr v-for="u in users" :key="u.id">
<td>{{u.id}}</td>
<td>{{u.name}}</td>
<td>{{u.age}}</td>
<td>{{u.gender}}</td>
</tr>
</tbody>
</template>
<template id="my-table">
<table border="1" align="center">
<thead is="table-head"></thead>
<tbody is="table-body"></tbody>
</table>
</template>
<script>
var tableHead = {
template:"#table-head"
}
var tableBody = {
template:"#table-body",
data:function(){
return {
users: [
{"id":1, "name": "小明", "age": 13, "gender": "男"},
{"id":2, "name": "小红", "age": 13, "gender": "女"},
{"id":3, "name": "小绿", "age": 4, "gender": "男"}
]
}
}
}
//使用局部注册,需要将子组件注册到父组件中
var myTable ={
template:"#my-table",
components:{
"table-head":tableHead,
"table-body":tableBody
}
}
const vm = new Vue({
el:"#app",
components:{
"my-table":myTable
}
});
</script>
说明:事实上,虽然 new Vue() 没有显式的使用组件的语法,但它本质上也是一个父组件。
组件通信
通常一个较为复杂的页面,一定会出现组件的嵌套。各个组件之间以嵌套的关系组合在一起,那么这个时候不可避免的会有组件间通信的需求。
1. 父传子:props
通常父组件的模板中包含子组件,父组件要正向地向子组件传递数据或参数,子组件接收到后根据参数的不同来渲染不同的内容或执行操作。这个正向传递数据的过程就是通过props来实现的。
比如在之前的表格案例中,table-body子组件展示的数据是定义在子组件自个身上的,这么做虽有效果,但降低了该组件的复用价值,更好的做法:子组件中不定义数据,而是由使用它的父组件传递。此时,需要使用 props完成父组件向子组件的数据传递。
语法:
//1.定义子组件中添加props属性
const 组件 = {
template:“html片段”,
props:[“自定义参数名”,…]
}
//2 使用组件时,为组件添加 自定义参数名 同名的属性
<组件 :自定义参数名=”值"></组件>
例子:
<div id="app">
<my-table :us="users"></my-table>
</div>
<template id="my-table">
<table border="1" align="center">
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>age</th>
<th>gender</th>
</tr>
</thead>
<tbody>
<tr v-for="u in us" :key="u.id">
<td>{{u.id}}</td>
<td>{{u.name}}</td>
<td>{{u.age}}</td>
<td>{{u.gender}}</td>
</tr>
</tbody>
</table>
</template>
<script>
var myTable = {
template:"#my-table",
props:["us"]
}
const vm = new Vue({
el:"#app",
data:{
users: [
{"id":1, "name": "小明", "age": 13, "gender": "男"},
{"id":2, "name": "小红", "age": 13, "gender": "女"},
{"id":3, "name": "小绿", "age": 4, "gender": "男"}
]
},
components:{
"myTable":myTable
}
});
</script>
2. 子传父:$emit
父组件的模板中包含子组件,那么经常会出现子组件的状态发生变化时,要通知到父组件。所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。如下所示:
代码如下:
<div id="app">
<h2>{{num}}</h2>
<!--
父组件向子组件传递数据 count=num
子组件中修改count值,不能修改父组件的num
-->
<counter :count="num"></counter>
</div>
<script>
Vue.component("counter",{
template:`
<div>
<button @click="increment">点我自增</button>
<span>{{count}}</span>
<button @click="decrement">点我自减</button>
</div>`,
props:["count"],
methods:{
increment(){
this.count++;
},
decrement(){
this.count--;
}
}
})
const vm = new Vue({
el:"#app",
data:{
num:0
}
})
</script>
所以,当需要子传父的时候,Vue采用事件放射的方式完成。
- 在子组件中执行 $emit(“父组件的自定义事件”)通知父组件,并发送数据
- 父组件中定义自定义事件处理函数,并接收数据
<div id="app">
<h2>{{num}}</h2>
<!-- 2 绑定自定义事件处理函数,监听子组件的事件触发 change-count
当子组件触发事件后,执行handleChange事件处理函数,
$emit发送的数据将成为handleChange的实参
-->
<counter :count="num" @change-count="handleChange"></counter>
</div>
<script>
Vue.component("counter",{
template:`
<div>
<button @click="increment">点我自增</button>
<span>{{count}}</span>
<button @click="decrement">点我自减</button>
</div>`,
props:["count"],
methods:{
increment(){
this.count++;
//1 子组件中通过$emit触发父组件中的自定义事件,并发送数据
this.$emit("change-count",this.count);
},
decrement(){
this.count--;
//1 子组件中通过$emit触发父组件中的自定义事件,并发送数据
this.$emit("change-count",this.count);
}
}
})
const vm = new Vue({
el:"#app",
data:{
num:0
},
methods:{
handleChange(value){
this.num = value;
}
}
})
</script>
实战案例
代码如下:
<div id="app">
<div>
<my-table :us="users" @show-user="handleShowUser"></my-table>
<hr>
<update-form :u="user" @update-user="handleUpdateUser"></update-form>
</div>
</div>
<template id="my-table">
<table border="1">
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>age</th>
<th>operation</th>
</tr>
</thead>
<tbody>
<tr v-for="(u,index) in us" :key="u.id">
<td>{{u.id}}</td>
<td>{{u.name}}</td>
<td>{{u.age}}</td>
<td>
<button @click="emitShowUser(u)">更新</button>
</td>
</tr>
</tbody>
</table>
</template>
<template id="update-form">
<form action="" @submit.prevent="emitUpdateUser">
<input type="hidden" v-model="u.id" >
用户名: <input type="text" v-model="u.name"> <br>
年龄: <input type="number" v-model="u.age"> <br>
<input type="submit" value="更新">
</form>
</template>
<script>
const updateForm = {
template:"#update-form",
props:["u"],
methods:{
emitUpdateUser(){
this.$emit("update-user",this.u);
}
}
};
const myTable = {
template:"#my-table",
props:["us"],
methods:{
emitShowUser(u){
this.$emit("show-user",u);
}
}
};
const vm = new Vue({
el:"#app",
data:{
users:[
{id:1,name:"xiao1hei",age:18},
{id:2,name:"xiao2hei",age:20},
{id:3,name:"xiao3hei",age:22}
],
user:{}
},
components:{
myTable,updateForm
},
methods:{
handleShowUser(u){
this.user = JSON.parse(JSON.stringify(u));
},
handleUpdateUser(u){
this.users.forEach((item,i,users)=>{
if(item.id == u.id){
//由于Vue2.x无法直接检测到数组中元素的变化,可以使用vue提供的$set方法
this.$set(this.users,i,JSON.parse(JSON.stringify(u))g);
}
})
}
}
})
</script>