vue中的12种组件通讯方式
vue中父子组件传值
vue中的父子组件传值,值得注意的是要遵守单向数据流原则。所谓单向数据流原则,简单的说就是父组件的数据可以传递给子组件,子组件也可以正常获取并使用由父组件传过来的数据;但是,子组件中不能直接修改父组件传过来的数据,必须要向父组件传递一个事件来父组件需要修改数据,即通过子组件的操作,在父组件中修改数据;这就是单项数据流。
1.父传子
父组件的数据、方法传递给子组件,子组件通过props接收,可以是数组、对象。
实现代码如下:
子组件:
<template>
<div>
<p>我是{{msg.name}},我今年{{msg.age}}岁了</p>
</div>
</template>
<script>
export default {
//在子组件中注册props,并使用父组件中传递过来的数据
props:{
msg:{
required:true,
type:Object
}
}
}
</script>
父组件:
<template>
<div>
<son :msg="list" ></son>
</div>
</template>
<script>
// 父组件中引入子组件,并传入子组件内需要的值
import son from './components/son.vue';
export default {
components: { son },
data() {
return {
//定义需要传入的值
list: {
name: "小明",
age: 18,
},
};
},
};
</script>
2子传父
- this.$emit('')可以触发一个自定义的事件
实现代码如下:
子组件:
<template>
<button @click="bt"> 我是子组件 </button>
</template>
<script>
export default {
data(){
return{
say:'hello,你好啊'
}
},
methods: {
bt() {
this.$emit('fn',this.say)
},
},
}
</script>
父组件:
<template>
<div>
<p>我是父组件 {{ msg }}</p>
<son @fn='btn'></son>
</div>
</template>
<script>
import son from './components/son.vue';
export default {
data(){
return{
msg:''
}
},
components: { son },
methods: {
btn(obj) {
this.msg = obj
},
},
};
</script>
3 v-model 实现父子组件传值
v-model是语法糖, v-model等价于 给一个input框提供了 :value属性以及 @input事件,但是如果每次使用input框,都需要提供value和input事件比较麻烦,所以使用v-model。
实现代码:
子组件:
<template>
<div>
我是子组件:
<input type="text" :value="value" @input="changeVal" />
</div>
</template>
<script>
export default {
name: "child",
props: ["value"],
methods: {
changeVal(e) {
this.$emit("input", e.target.value);
},
},
};
</script>
父组件:
<template>
<div class="parent">
<h1>我是父组件:{ { msg }}</h1>
<child v-model="msg"></child>
</div>
</template>
<script>
import child from "./child";
export default {
name: "parent",
components: {
child,
},
data() {
return {
msg: "hello!",
};
},
};
</script>
4.sync 修饰符 实现父子组件传值
.sync 修饰符 是vue 2.3.0+ 新增的特性(但是在vue3.0中被废弃)
实现代码如下
子组件:(以 update:myPropName 的模式触发事件)
<template>
<div>
我是子组件:
<input type="text" :value="msg" @input="changeValFn" />
</div>
</template>
<script>
export default {
name: 'child',
props: ['msg'],
methods: {
changeValFn(e) {
this.$emit('update:msg', e.target.value)
},
},
}
</script>
父组件:(以v-bind:msg.sync="msg"的模式传递/接收数据)
<template>
<div class="parent">
<h1>我是父组件:{ { msg }}</h1>
<!-- <child v-bind:msg.sync="msg"></child> -->
<child :msg.sync="msg"></child>
</div>
</template>
<script>
import child from "./child";
export default {
name: "parent",
components: {
child,
},
data() {
return {
msg: "hello!",
};
},
};
</script>
5 兄弟组件通信
EventBus
第一种方法:
第一步: 在父组件中给要传值的兄弟两个都绑定要传的变量。
<app-user-detail :age="age"></app-user-detail>
<app-user-edit :age="age" @editAge="changeAge"></app-user-edit>
第二步: 在要传值得组件中接受变量和绑定触发事件
<template>
<div>
<h3>用户编辑页面</h3>
<p>编辑用户</p>
<p>年龄: {{ age }}</p>
<p><button @click="changeAge">编辑年龄</button></p>
</div>
</template>
<script>
export default {
props: ["age"],
methods: {
changeAge: function() {
this.$emit('editAge', 20) // 触发自定义事件并传值
}
}
}
</script>
```
第三步: 在父组件中绑定要传组件中的自定义事件
export default {
data() {
return {
name: 'zhuli',
age: 10,
}
},
components: {
'app-user-detail': UserDetail,
'app-user-edit': UserEdit,
},
methods: {
changeAge: function(age) {
this.age = age
},
}
}
总结: 当要传值的组件改变了父组件的变量,父组件又可以把改变的值传值所绑定变量的组件,实现兄弟间传值。
第二种方式: 和上面子组件传值父组件一样,使用一个函数,再函数中传参使用。
小结: 这两种方式的缺点是嵌套层级深,不好处理。
第三种方式: 后续应该使用vuex
第四种方式: 思想: 既然要借助父级,那么可不可不定义一个全局事件
第一步: 在入口文件main.js里暴露一个vue实例
export const EventBus = new Vue() // 暴露一个vue实例
第二步: 在要传值的文件里导入vue实例模块,age使用自己的变量,再定义一个全局触发事件函数,触发事件函数绑定在一个button上
<template>
<div>
<h3>用户编辑页面</h3>
<p>编辑用户</p>
<p>年龄: {{ age }}</p>
<p><button @click="changeAge">编辑年龄</button></p>
</div>
</template>
<script>
import { EventBus } from "../main.js"
// 导入模块
export default {
// props: ["age"],
data() {
return {
age: 10,
}
},
methods: {
changeAge: function() {
this.age = 20
EventBus.$emit("editAge", this.age)
// 触发全局事件 并且把改变后的值传入事件函数
// console.log(EventBus)
}
}
}
</script>
第三步: 在要被传入值得组件中也导入vue实例模块,也不使用父组件中传过来个age,自己重新定义,创建一个初始化的钩子函数,再使用created钩子函数中使用传值组件的全局定义事件。
import { EventBus } from "../main.js"
export default {
data() {
return {
name : this.myName,
age: 10
}
},
methods: {
changeParentName: function() {
this.$emit('changeParentName', 'xiaohong')
// this.$emit('')触发自定义事件
},
created() {
EventBus.$on('editAge', (age) => { // 使用$on去绑定事件
this.age = age // 使用es6写法,this刚好指向父级
})
}
}
总结: 现在兄弟之间传值,都没有通过父组件
6 provide / inject
//祖先组件
import { provide } from 'vue';
const { separtor = '>'} = defineProps<{separtor?:string}>()
// 祖先级别的传值
provide('separtor',separtor)
//后代组件
import { inject } from 'vue';
defineProps<{to?:string}>()
const separtor = inject('separtor')
7ref传值
父组件可以通过 ref 主动获取子组件的属性或者调用子组件的方法
// Child.vue
export default {
data(){
return {
name:"oldCode"
}
},
methods:{
someMethod(msg){
console.log(msg)
}
}
}
// Parent.vue
<template>
<child ref="child"></child>
</template>
<script>
export default {
mounted(){
const child = this.$refs.child
console.log(child.name)
child.someMethod("调用了子组件的方法")
}
}
</script>
8 $attrs / $listeners
当要和一个嵌套很深的组件进行通信时,如果使用 prop 和 events 就会显的十分繁琐,中间的组件只起到了一个中转站的作用,像下面这样:
<!--父组件-->
<parent-component :message="message">我是父组件</parent-component>
<!--子组件-->
<child-component :message="message">我是子组件</child-component>
<!--孙子组件-->
<grand-child-component :message="message">我是孙子组件</grand-child-component>
当要传递的数据很多时,就需要在中间的每个组件都重复写很多遍,反过来从后代组件向祖先组件使用 events 传递也会有同样的问题。使用 $attrs 和 $listeners 就可以简化这样的写法。
$attrs 会包含父组件中没有被 prop 接收的所有属性(不包含class 和 style 属性),可以通过 v-bind="$attrs" 直接将这些属性传入内部组件。
$listeners 会包含所有父组件中的 v-on 事件监听器 (不包含 .native 修饰器的) ,可以通过 v-on="$listeners" 传入内部组件。
下面以父组件和孙子组件的通信为例介绍它们的使用:
<!--父组件 parent.vue-->
<template>
<child :name="name" :message="message" @sayHello="sayHello"></child>
</template>
<script>
export default {
inheritAttrs: false,
data() {
return {
name: '通信',
message: 'Hi',
}
},
methods: {
sayHello(mes) {
console.log('mes', mes) // => "hello"
},
},
}
</script>
<!--子组件 child.vue-->
<template>
<grandchild v-bind="$attrs" v-on="$listeners"></grandchild>
</template>
<script>
export default {
data() {
return {}
},
props: {
name,
},
}
</script>
<!--孙子组件 grand-child.vue-->
<template>
</template>
<script>
export default {
created() {
this.$emit('sayHello', 'hello')
},
}
</script>
9$children / $parent
$parent 属性可以用来从一个子组件访问父组件的实例,$children 属性 可以获取当前实例的直接子组件。
看起来使用 $parent 比使用prop传值更加简单灵活,可以随时获取父组件的数据或方法,又不像使用 prop 那样需要提前定义好。但使用 $parent 会导致父组件数据变更后,很难去定位这个变更是从哪里发起的,所以在绝大多数情况下,不推荐使用。
在有些场景下,两个组件之间可能是父子关系,也可能是更多层嵌套的祖孙关系,这时就可以使用 $parent。
下面是 element ui 中的组件 el-radio-group 和 组件 el-radio 使用示例:
<template>
<el-radio-group v-model="radio1">
<el-radio :label="3">备选项</el-radio>
<component-1>
<el-radio :label="3">备选项</el-radio>
</component-1>
</el-radio-group>
</template>
<script>
export default {
data () {
return {
radio2: 3
};
}
}
</script>
在 el-radio-group 和 组件 el-radio 通信中, 组件 el-radio 的 value 值需要和 el-radio-group的 v-model 的值进行“绑定”,我们就可以在 el-radio 内借助 $parent 来访问到 el-radio-group 的实例,来获取到 el-radio-group 中 v-model 绑定的值。
下面是获取 el-radio 组件中获取 el-radio-group 实例的源码:
// el-radio组件
let parent = this.$parent;
while (parent) {
if (parent.$options.componentName !== 'ElRadioGroup') {
parent = parent.$parent;
} else {
this._radioGroup = parent; // this._radioGroup 为组件 el-radio-group 的实例
}
}
10 通过 $root 访问根实例
通过 $root,任何组件都可以获取当前组件树的根 Vue 实例,通过维护根实例上的 data,就可以实现组件间的数据共享。
//main.js 根实例
new Vue({
el: '#app',
store,
router,
// 根实例的 data 属性,维护通用的数据
data: function () {
return {
author: ''
}
},
components: { App },
template: '<App/>',
});
<!--组件A-->
<script>
export default {
created() {
this.$root.author = '于是乎'
}
}
</script>
<!--组件B-->
<template>
<div><span>本文作者</span>{{ $root.author }}</div>
</template>
11Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。对一个中大型单页应用来说是不二之选。
使用 Vuex 并不代表就要把所有的状态放入 Vuex 管理,这样做会让代码变的冗长,无法直观的看出要做什么。对于严格属于组件私有的状态还是应该在组件内部管理更好。
对于小型的项目,通信十分简单,这时使用 Vuex 反而会显得冗余和繁琐,这种情况最好不要使用 Vuex,可以自己在项目中实现简单的 Store。
//store.js
var store = {
debug: true,
state: {
author: 'yushihu!'
},
setAuthorAction (newValue) {
if (this.debug) console.log('setAuthorAction triggered with', newValue)
this.state.author = newValue
},
deleteAuthorAction () {
if (this.debug) console.log('deleteAuthorAction triggered')
this.state.author = ''
}
}
和 Vuex 一样,store 中 state 的改变都由 store 内部的 action 来触发,并且能够通过 log 保留触发的痕迹。这种方式十分适合在不需要使用 Vuex 的小项目中应用。
12 插槽 slot 与子组件传值
在实际项目中确实有遇到插槽 后备内容 动态显示的情况,所以这里要补充一下插槽 后备内容 是如何与子组件进行通信的。
插槽后备内容是指:写在父组件中,包含在子组件标签里的,与子组件中的 slot 对应。
<template>
<child-component>
我是插槽的后备内容
</child-component>
</template>
复制代码
比如这里有一个含有 slot 的 current-user 组件,它的模版结构是这样的:
<!-- 子组件 current-user.vue -->
<template>
<div>
<div>current-user组件</div>
<slot>插槽里默认显示:{{user.firstName}}</slot>
</div>
</template>
<script>
export default {
data() {
return {
user: {
firstName: "zhao",
lastName: "xinglei"
}
}
}
}
</script>
复制代码
它的父组件是这样的:
<!-- 父组件 Users.vue -->
<template>
<div>
<div>我是Users组件</div>
<current-user>
我是插槽里的后备内容: {{user.lastName}}(我想显示为子组件中 user.lastName )
</current-user>
</div>
</template>
<script>
import CurrentUser from './Current-User.vue'
export default {
components: {
CurrentUser
}
}
</script>
复制代码
我们看到,在父组件 Users 中,为子组件 current-user 提供的后备内容中,想要显示子组件定义的 user.firstName 是不能做到的。
官网中提供一个指令 v-slot,它与 props 结合使用从而达到插槽后备内容与子组件通信的目的。
我们首先需要在子组件的 slot 中传递一个 props(这个props 叫做插槽props),这里我们起名叫 user:
<!-- 子组件 current-user.vue -->
<template>
<div>
<div>current-user组件</div>
<slot :user="user">
插槽里默认显示:{{user.firstName}}
</slot>
</div>
</template>
复制代码
在父组件中,包含插槽后备内容的子组件标签上我们绑定一个 v-slot 指令,像这样:
<template>
<div>
<div>我是Users组件</div>
<!-- slotProps里的内容就是子组件传递过来的 props -->
<!-- "user": { "firstName": "zhao", "lastName": "xinglei" } -->
<current-user v-slot="slotProps">
{{slotProps}}
</current-user>
</div>
</template>
复制代码
最后渲染出来的结果为:
官网给这种插槽起名叫做作用域插槽,
总结
1. 组件之间传值无非就是通过属性、事件和操作 Vue 实例进行的。
2. 操作实例进行组件件通信,实例属性 $root、$parent、$children 分别对应了根实例、父实例、子实例。
3 ref 子组件引用,在操作表单元素时会应用的到。
4. Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,简单的应用不要使用 Vuex。