一、概括
在通信中,无论是子组件向父组件传值还是父组件向子组件传值,他们都有一个共同点就是有中间介质,子向父的介质是自定义事件,父向子的介质是props中的属性。抓准这两点对于父子通信就好理解了。
绑定事件监听
// 方式一: 通过 v-on 绑定
@delete_todo="deleteTodo"
// 方式二: 通过$on();生命周期监听自定义事件
this.$refs.xxx.$on('delete_todo',function(todo){
this.deleteTodo(todo)
})
注意:this.$on(['delete_todo','delete_todo2']) 可以监听多个事件
触发事件
// 触发事件(只能在父组件中接收)
this.$emit(eventName,data)
注意:
1) 此方式只用于子组件向父组件发送消息(数据)
2) 问题: 隔代组件或兄弟组件间通信此种方式不合适
$emit 传递多个参数
方法一:将要传的数据放到对象中,再将对象传给父组件
子组件:
let obj = {
data1: '111',
data2: '222'
}
this.$emit('getData',obj)
父组件:
<child @getData="getData"></child>
getData(data){
console.log(data) // {data1:'111',data2:'222'}
}
方法二:直接传递多个参数
子组件:
this.$emit('getData','111','222')
父组件:
接收的时候要传 arguments 参数
<child @getData="getData(arguments)"></child>
getData(data){
console.log(data[0],data[1]) // '111' '222'
}
二、案例
方式一:@xxx='xxxx'
案例:
第一步:在子组件中创建一个按钮,给按钮绑定一个点击事件
第二步:在响应该点击事件的函数中使用$emit来触发一个自定义事件,并传递一个参数(自定义事件名)
第三步:在父组件中的子标签中监听该自定义事件并添加一个响应该事件的处理方法
第四步:使用传递过来的数据(父组件中响应的事件参数)
child.vue
<template>
<div class="child">
<h2>child子组件</h2>
<!-- 第一步 -->
<button @click="sendMsgToParent">点击向父组件传数据</button>
</div>
</template>
<script>
export default {
data() {
return {
message: '子组件的data'
}
},
methods: {
/**
* 第二步
*/
sendMsgToParent() {
//在子组件中使用$emit(eventName,data)来触发一个自定义事件,并传递一个参数
this.$emit('listenToChildEvent',this.message);
}
}
}
</script>
parent.vue
<template>
<div class="parent">
<h1>子传父emit</h1>
<hr>
<!--第三步
定义一个listenToChildEvent自定义事件监听子组件的触发
在父组件中的子标签中监听该自定义事件并添加一个响应该事件的处理方法 -->
<child @listenToChildEvent='showMsgFromChild'></child>
<p>来自于子组件的数据为:{{fromChildData}}</p>
</div>
</template>
<script>
import child from './child'
export default {
data() {
return {
fromChildData: ''
}
},
components:{
child
},
methods: {
/**
* 第四步
* 响应事件的参数为子组件传递过来的值
*/
showMsgFromChild(data) {
this.fromChildData = data
}
}
}
</script>
方式二:$refs.xxx.$on()对事件进行监听
// 方式二: 通过$on();生命周期监听自定义事件
this.$refs.xxx.$on('delete_todo',function(todo){
this.deleteTodo(todo)
})
或者this.$on();进行 事件的监听,可以监听多个
child.vue
<template>
<div class="child">
<div>
<button @click.stop="childToFatherClick">点击子组件</button>
</div>
</div>
</template>
<script>
export default {
name: "child",
data() {
return {
msg: { id: 1, name: "father", age: 53, sex: "男" }
};
},
methods: {
childToFatherClick() {
this.$emit("childToFatherData", this.msg);
}
}
};
</script>
parent.vue
<template>
<div class="childToFather">
<h3>方式二</h3>
<!--用ref给子组件起个名字-->
<child ref="child02"></child>
<p v-if="msg1.id">{{msg1}}</p>
</div>
</template>
<script>
import child from "../../../components/child";
export default {
name: "childToFather",
components: {
child
},
data() {
return {
msg1: {}
};
},
mounted() { // 子组件触发自定义事件,挂载到mounted上实现子传父
let _this = this;
this.$refs.child02.$on("childToFatherData", function(data) {
console.log(data);
// 需要注意this指向问题
_this.msg1 = data;
});
},
};
</script>
方式三:.sync
上面的语法糖
vue中我们经常会用v-bind(缩写为:)给子组件传入参数。
或者我们会给子组件传入一个函数,子组件通过调用传入的函数来改变父组件的状态。
例如:
//父组件给子组件传入一个函数
<MyFooter :age="age" @setAge="(res)=> age = res">
</MyFooter>
//子组件通过调用这个函数来实现修改父组件的状态。
mounted () {
console.log(this.$emit('setAge',1234567));
}
这时子组件触发了父组件的修改函数使父组件的age修改成了1234567
这种情况比较常见切写法比较复杂。于是我们引出今天的主角 .sync
vue .sync的历史
vue .sync 修饰符最初存在于 vue 1.0 版本里,但是在 2.0 中被移除了。但是在 2.0 发布之后的实际应用中,vue 官方发现 .sync 还是有其适用之处,比如在开发可复用的组件库时。开发者需要做的只是让子组件改变父组件状态的代码更容易被区分。从 2.3.0 起官方重新引入了 .sync 修饰符,但是这次它只是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的 v-on 监听器。 .sync 可以有多个
这时我们可以直接这样写
//父组件将age传给子组件并使用.sync修饰符。
<MyFooter :age.sync="age">
</MyFooter>
// 子组件事件触发 update:固定写法 (update:props名称, 值)
mounted () {
console.log(this.$emit('update:age',1234567));
}
这里注意我们的事件名称被换成了 update:age
update:是被固定的也就是vue为我们约定好的名称部分
age:是我们要修改的状态的名称,是我们手动配置的,与传入的props状态名字对应起来
这样就完成了,是不是感觉简单了很多。
注意事项:
这里我们必须在事件执行名称前加上update:的前缀才能正确触发事件。
三、vue子组件调用父组件中的方法
有时候,我们会把方法定义在父组件中,然后在多个子组件中调用父组件的方法,这是很常见的场景。那么,在子组件中调用父组件的方法有哪几种方式呢?
1. provide/inject
在某些场景下,我们的组件嵌套层级会非常深,这个时候如果使用 props 的方式传递祖先组件的方法或属性,并不是一个明智的选择,我们应该使用 provide/inject 来跨层级访问祖先组件的数据。
provide 向子孙组件提供父组件的方法或属性。provide 是一个对象或返回一个对象的函数,该对象包含了可注入其子孙的 property。
inject 是一个字符串数组或一个对象
使用方式:
- 祖先组件里提供
provide
- 子孙组件里,
inject
相应的名字即可
父组件
// Parent.vue
<template>
<div class="container">
<h3>provide/inject 传递方法</h3>
<div class="description">
在父组件中通过 provide 向子孙组件提供方法,在子孙组件中通过 inject
获取父组件提供的方法
</div>
<br />
<Child></Child>
</div>
</template>
<script>
import Child from "./Children";
export default {
name: "Father",
components: {
Child,
},
provide() {
return {
fatherMethod: this.fatherMethodHandle,
};
},
methods: {
fatherMethodHandle() {
console.log("我是父组件的方法");
},
},
};
</script>
<style scoped lang="scss">
</style>
子组件
// Child.vue
<template>
<div>
<button @click="childMethod">我是子组件</button>
</div>
</template>
<script>
export default {
name: "Child",
props: {
msg: String,
},
inject: ["fatherMethod"],
methods: {
childMethod() {
console.log("我是子组件的方法,我在子组件中调用了父组件的方法");
this.fatherMethod();
},
},
};
</script>
<style scoped lang="scss">
</style>
注意:通过 provide/inject 注入的属性在子孙组件中是无法使用 watch的
2. $props
在父组件中通过 props 的方式传入子组件中,在子组件中直接调用这个方法。在嵌套层级很深的子组件中不建议使用 props 的方式传递父组件的方法,因为层层传递会导致代码变得难以维护。
父组件
// Parent.vue
<template>
<div class="container">
<h3>props 方式传递方法</h3>
<div class="description">
在父组件中通过 props 的方式 向子孙组件传递方法
</div>
<br />
<Child :fatherMethod="fatherMethodHandle"></Child>
</div>
</template>
<script>
import Child from "./Child";
export default {
name: "Father",
components: {
Child,
},
methods: {
fatherMethodHandle() {
console.log("我是父组件的方法");
},
},
};
</script>
<style scoped lang="scss">
</style>
子组件
// Child.vue
<template>
<div>
<button @click="childMethod">我是子组件</button>
</div>
</template>
<script>
export default {
name: "Child",
props: {
fatherMethod: {
type: Function,
require: true,
default: null,
},
},
methods: {
childMethod() {
console.log(
"我是子组件的方法,我在子组件中通过 props 的方式调用了父组件的方法"
);
this.fatherMethod();
},
},
};
</script>
<style scoped lang="scss">
</style>
3. $parent
在子组件中通过 this.$parent.event
的方式来调用父组件的方法。在嵌套层级很深的子组件中不建议使用该方式。
父组件
// Parent.vue
<template>
<div class="container">
<h3>$parent 获取父组件方法</h3>
<div class="description">
在子组件中通过 Vue 实例的 $parent 方法获取父组件的方法
</div>
<br />
<Child></Child>
</div>
</template>
<script>
import Child from "./Child";
export default {
name: "Father",
components: {
Child,
},
methods: {
fatherMethodHandle() {
console.log("我是父组件的方法");
},
},
};
</script>
<style scoped lang="scss">
</style>
子组件
// Child.vue
<template>
<div>
<button @click="childMethod">我是子组件</button>
</div>
</template>
<script>
export default {
name: "Child",
methods: {
childMethod() {
console.log(
"我是子组件的方法,我在子组件中通过 $parent 调用了父组件的方法"
);
this.$parent.fatherMethodHandle();
},
},
};
</script>
<style scoped lang="scss">
</style>