Vue2父子组件通信
2023-08-11 本文已影响0人
h2coder
父组件和子组件通信
- props传参和$emit发送事件
- EventBus事件总线
- provide和inject
- 通过$ref
使用props传参
父向子传参
子组件(Son.vue)
- 子组件,在
props
属性中,声明要接受的参数
<template>
<div class="son" style="border: 3px solid #000; margin: 10px">
<!-- 子组件,使用父组件传过来的数据 -->
我是Son组件 {{ title }}
</div>
</template>
<script>
export default {
// 子组件,声明要接收的属性
props: ["title"],
};
</script>
<style>
</style>
父组件(App.vue)
- 父组件,通过v-bind传递参数
<template>
<div class="app" style="border: 3px solid #000; margin: 10px">
我是APP组件
<!-- 使用子组件,并通过属性,传递data中的 myTitle 数据给子组件 -->
<HmSon :title="myTitle"></HmSon>
</div>
</template>
<script>
// 引入子组件
import HmSon from "./components/HmSon.vue";
export default {
name: "App",
data() {
return {
// 父组件的数据
myTitle: "学前端",
};
},
// 局部注册子组件
components: {
HmSon,
},
};
</script>
<style>
</style>
子向父传参
父组件(App.vue)
- 父组件通过v-bind,绑定子组件的title属性,通过v-on监听子组件发出的事件,来达到子组件通知父组件
<template>
<div class="app" style="border: 3px solid #000; margin: 10px">
我是APP组件,选中: {{ selectText }}
<!-- 父组件,监听子组件的自定义事件 -->
<HmSon :title="myTitle1" @changeHero="onChangeHero"></HmSon>
<HmSon :title="myTitle2" @changeHero="onChangeHero"></HmSon>
<HmSon :title="myTitle3" @changeHero="onChangeHero"></HmSon>
</div>
</template>
<script>
import HmSon from "./components/HmSon.vue";
export default {
name: "App",
data() {
return {
myTitle1: "1号选手,刘xx",
myTitle2: "2号选手,罗xx",
myTitle3: "3号选手,万xx",
// 选中的选手名称
selectText: "",
};
},
methods: {
// 处理子组件的事件
onChangeHero(msg) {
this.selectText = msg;
},
},
components: {
HmSon,
},
};
</script>
<style>
</style>
子组件(Son.vue)
- 子组件使用vue实例身上的
$emit
函数,发送一个changeHero
自定义事件,并携带事件参数title
<template>
<div class="son" style="border: 3px solid #000; margin: 10px">
{{ title }}
<!-- 子组件,点击按钮时,发送自定义事件给父组件 -->
<button @click="changeHero">选我</button>
</div>
</template>
<script>
export default {
props: ["title"],
methods: {
changeHero() {
// 子组件,发送自定义事件给父组件
// 语法:this.$emit('事件名称', '参数')
this.$emit("changeHero", this.title);
},
},
};
</script>
<style>
</style>
使用props传递多个值
父组件(App.vue)
- 和传递单个参数类似,通过v-bind传递多个参数即可
<template>
<div class="app">
<UserInfo
:username="username"
:age="age"
:isSingle="isSingle"
:car="car"
:hobby="hobby"
></UserInfo>
</div>
</template>
<script>
import UserInfo from './components/UserInfo.vue'
export default {
data() {
return {
username: '小帅',
age: 28,
isSingle: true,
car: {
brand: '宝马',
},
hobby: ['篮球', '足球', '羽毛球'],
}
},
components: {
UserInfo,
},
}
</script>
<style>
</style>
子组件(UserInfo.vue)
<template>
<div class="userinfo">
<h3>我是个人信息组件</h3>
<div>姓名:{{ username }}</div>
<div>年龄:{{ age }}</div>
<div>是否单身:{{ isSingle }}</div>
<div>座驾:{{ car.brand }}</div>
<div>兴趣爱好:{{ hobby.join("、") }}</div>
</div>
</template>
<script>
export default {
props: ["username", "age", "isSingle", "car", "hobby"],
};
</script>
<style>
.userinfo {
width: 300px;
border: 3px solid #000;
padding: 20px;
}
.userinfo > div {
margin: 20px 10px;
}
</style>
给props参数设置类型
父组件(App.vue)
<template>
<div class="app">
<!-- 不传参,使用默认值 -->
<BaseProgress></BaseProgress>
<!-- 传参 -->
<BaseProgress :w="width"></BaseProgress>
</div>
</template>
<script>
import BaseProgress from "./components/BaseProgress.vue";
export default {
data() {
return {
// 正确
width: 80,
// 传错为字符串类型
// width: "60",
};
},
components: {
BaseProgress,
},
};
</script>
<style>
</style>
子组件(BaseProgress.vue)
<template>
<div class="base-progress">
<div class="inner" :style="{ width: w + '%' }">
<span>{{ w }}%</span>
</div>
</div>
</template>
<script>
export default {
// 写法一,不校验类型,只声明变量名
// props: ["w"],
// 写法二:属性:类型。作用:限定参数的数据类型,参数类型不一致,则报错
// props: {
// w: Number,
// },
// 写法三,更新完整,能设置参数类型、是否必传、默认值、参数校验函数
props: {
w: {
// 参数类型
type: Number,
// 参数是否必传
// required: true,
// 默认值
default: 60,
// 校验函数,value参数,就是父组件传递过来的参数,返回true,则校验通过,否则不通过
validator(value) {
return value > 50;
},
},
},
};
</script>
<style scoped>
.base-progress {
height: 26px;
width: 400px;
border-radius: 15px;
background-color: #272425;
border: 3px solid #272425;
box-sizing: border-box;
margin-bottom: 30px;
}
.inner {
position: relative;
background: #379bff;
border-radius: 15px;
height: 25px;
box-sizing: border-box;
left: -3px;
top: -2px;
}
.inner span {
position: absolute;
right: 0;
top: 26px;
}
</style>
使用EventBus
- EventBus,不限制组件的层级关系,只要能拿到EventBus的实例,就能监听事件和发送事件,以下演示一个组件发送事件,另外2个兄弟组件监听事件
EventBus.js
// 导入Vue
import Vue from 'vue';
// 创建一个Vue实例,作为事件总线
const Bus = new Vue();
// 默认导出
export default Bus;
父组件(App.vue)
- 父组件里面,有3个子组件,B组件负责发送事件,组件A和组件C负责监听事件
<template>
<div class="app">
<BaseA></BaseA>
<BaseB></BaseB>
<BaseC></BaseC>
</div>
</template>
<script>
import BaseA from './components/BaseA.vue'
import BaseB from './components/BaseB.vue'
import BaseC from './components/BaseC.vue'
export default {
components:{
BaseA,
BaseB,
BaseC
}
}
</script>
<style>
</style>
组件B(BaseB.vue)
- 导入EventBus,通过
$emit
函数,发送事件,并携带数据
<template>
<div class="base-b">
<div>我是B组件(发布方)</div>
<button @click="fn">发送消息</button>
</div>
</template>
<script>
// 导入EventBus
import Bus from "../utils/EventBus";
export default {
methods: {
// 发送自定义事件
fn() {
console.log('组件B,发送事件!');
Bus.$emit("sendMsg", "我是来自于B组件的数据");
},
},
};
</script>
<style scoped>
.base-b {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
}
</style>
组件A和组件C
- 组件A
<template>
<div class="base-a">
我是A组件(接收方)
<p>{{ msg }}</p>
</div>
</template>
<script>
import Bus from "../utils/EventBus";
export default {
data() {
return {
msg: "",
};
},
created() {
// 监听事件,更新组件变量
Bus.$on("sendMsg", (msg) => {
console.log(`组件A,接收到事件,数据:${msg}`);
this.msg = msg;
});
},
};
</script>
<style scoped>
.base-a {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
}
</style>
- 组件C
<template>
<div class="base-c">
我是C组件(接收方)
<p>{{ msg }}</p>
</div>
</template>
<script>
import Bus from "../utils/EventBus";
export default {
data() {
return {
msg: "",
};
},
created() {
// 监听事件,更新组件变量
Bus.$on("sendMsg", (msg) => {
console.log(`组件C,接收到事件,数据:${msg}`);
this.msg = msg;
});
},
};
</script>
<style scoped>
.base-c {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
}
</style>
provide和inject实现父子通信
- 注意:组件要求必须有父子关系、子孙关系,兄弟关系不能共享数据
- 如果提供的是简单数据类型(基础数据类型),为非响应式变量,当数据改变时,不会同步给子孙组件
- 如果提供的是引用数据类型,对象、数组等,则为响应式变量,数据改变时,将会同步给子孙组件
父组件(App.vue)
<template>
<div class="app">
我是APP组件 {{ color }} --- {{ userInfo.uName }}
<button @click="changeData">修改数据</button>
<SonA></SonA>
<SonB></SonB>
</div>
</template>
<script>
import SonA from "./components/SonA.vue";
import SonB from "./components/SonB.vue";
export default {
data() {
return {
// 注意:如果提供的时普通类型,是非响应式数据(也就是数据改变时,子孙组件不能能收到更新)
// 如果提供的是复杂类型,则是响应式数据(则是改变后,子孙的数据,可以跟着祖先改变)
color: "red",
userInfo: {
uName: "八戒",
},
};
},
// 祖孙组件提供数据,provide和inject是配套使用的
// 这种数据共享方式,要求组件的关系,必须是祖先和子孙关系,不能是纯兄弟关系(否则不生效)
provide() {
return {
color: this.color,
userInfo: this.userInfo,
};
},
methods: {
// 点击事件,更新数据
changeData() {
this.color = "blue";
this.userInfo.uName = "悟空";
},
},
components: {
SonA,
SonB,
},
};
</script>
<style>
.app {
border: 3px solid #000;
border-radius: 6px;
margin: 10px;
}
</style>
子组件(SonA.vue、SonB.vue)
父组件包裹2个子组件,SonA和SonB,其中SonA又包裹了一个孙组件GrandSon,SonB没有孙组件
- SonA
<template>
<div class="SonA">我是SonA组件
<GrandSon></GrandSon>
</div>
</template>
<script>
import GrandSon from '../components/GrandSon.vue'
export default {
components:{
GrandSon
}
}
</script>
<style>
.SonA {
border: 3px solid #000;
border-radius: 6px;
margin: 10px;
height: 200px;
}
</style>
- SonB
<template>
<div class="SonB">我是SonB组件 {{ color }} --- {{ userInfo.uName }}</div>
</template>
<script>
export default {
// 孙子组件,声明获取祖先组件的数据
inject: ["color", "userInfo"],
};
</script>
<style>
.SonB {
border: 3px solid #000;
border-radius: 6px;
margin: 10px;
height: 200px;
}
</style>
孙组件(GrandSon.vue)
<template>
<div class="grandSon">我是GrandSon {{ color }} --- {{ userInfo.uName }}</div>
</template>
<script>
export default {
// 孙子组件,声明获取祖先组件的数据
inject: ["color", "userInfo"],
};
</script>
<style>
.grandSon {
border: 3px solid #000;
border-radius: 6px;
margin: 10px;
height: 100px;
}
</style>
通过ref
-
Vue提供了
ref
给开发者可以给DOM元素打一个标记,并可以在mounted
回调后,获取DOM元素 -
ref
可以限定在组件内部获取DOM元素,而使用document.querySelector()
是在整个页面中查找元素,如果有多个元素都命中选择器,则很容易选错 -
语法:
- $refs.元素标记的ref.方法名或属性名,例如:
- this.$refs.baseForm.reset();
- this.$refs.baseForm.username = "Wally";
父组件(App.vue)
<template>
<div class="app">
<button @click="fn">清空 重置</button>
<!-- 通过ref属性,绑定子组件 -->
<BaseForm ref="baseForm"></BaseForm>
</div>
</template>
<script>
import BaseForm from "./components/BaseForm.vue";
export default {
methods: {
fn() {
// console.log(this.$refs.baseForm);
// 父组件中,调用子组件的方法
this.$refs.baseForm.reset();
// 也能操作子组件的数据(这也是一种父组件向子组件传参的方式,但比较少用)
this.$refs.baseForm.username = "wally";
},
},
components: {
BaseForm,
},
};
</script>
<style>
</style>
子组件(BaseForm.vue)
<template>
<form autocomplete="off">
<div>
<label for="">用户名<input type="text" v-model="username" /></label>
<label for="">密码<input type="text" v-model="password" /></label>
<button @click="reset">重置</button>
</div>
</form>
</template>
<script>
export default {
data() {
return {
username: "",
password: "",
};
},
methods: {
reset() {
this.username = "";
this.password = "";
},
},
};
</script>
<style>
</style>