7. vue组件间通信方式
父子组件间传值
props(vue3:defineProps)/$emit
父组件通过props
的方式向子组件传递数据,子组件通过$emit
向父组件通信
vue2
父组件
html
<!--父组件-->
<template>
<div class="section">
<com-article :articles="articleList" @onEmit='onemit'></com-article>
</div>
</template>
<script>
import Child from './test/article.vue'
export default {
name: 'HelloWorld',
components: { Child },
data() {
return {
articleList: ['红楼梦', '西游记', '三国演义']
}
},
methods: {
onemit(e) {
// 接受子组件传来的值
console.log(e) // 1
}
}
}
</script>
子组件
html
<template>
<div>
<span v-for="(item, index) in articles" :key="index">{{item}}</span>
<div @click='toemit'>向父组件传值</div>
</div>
</template>
<script>
export default {
// 接受父组件传来的props
props: ['articles'],
// 可以对props做基础的数据验证
// props: {
// propsA: {
// type: String/Number..,
// required: true
// default: 100
// default: function() {
// return {message: 100}
// }
// }
// }
methods: {
toEmit() {
// 向父组件传一个1
this.$emit('onemit', 1)
}
}
}
</script>
vue3 - Composition API
父组件
html
<child :msg2="msg2" @parentClick="parentClick" @parentChang="parentChang"></child>
<script setup>
import child from "./child.vue"
import { ref, reactive } from "vue"
const msg2 = ref("这是传给子组件的信息2")
// 或者复杂类型
const msg2 = reactive(["这是传给子组件的信息2"])
const parentClick = (data: number) => { alert('子组件触发了此事件,传递的值是' + data) }
const parentChang = () => { alert('子组件触发了此事件') }
</script>
子组件
html
// Child.vue 接收
<template>
<button @click="handClick">点击触发handClick事件</button>
<input type="checkbox" @chang="handChang">
</template>
<script setup>
// 不需要引入 直接使用
// import { defineProps } from "vue"
// 接受props
const props = defineProps({
// 写法一
msg2: String
// 写法二
msg2:{
type:String,
default:""
...
}
})
console.log(props) // { msg2:"这是传级子组件的信息2" }
const emits = defineEmits(['parentClick','parentChang']) // 语法糖
// 定义类型
// const emits = defineEmits<{
// (e: 'parentClick', data: number): void // 函数类型 当传递的值的类型不是number时会报错
// (e: 'parentChang'): void // 函数类型
// }>();
const handClick = () => {
emits('parentClick',2) // 使用方式和 vue2 this.$emit 一样
}
const handChang = () => {
emits('parentChang') // 使用方式和 vue2 this.$emit 一样
}
</script>
总结: prop 只可以从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且 prop 只读,不可被修改,所有修改都会失效并警告。
$children和$parent(仅vue2)
通过$parent
和$children
就可以访问组件的实例,就可以访问此组件的所有方法和数据
html
// 父组件中
<template>
<div class="hello_world">
<div>{{msg}}</div>
<com-a></com-a>
<button @click="changeA">点击改变子组件值</button>
</div>
</template>
<script>
import ComA from './test/comA.vue'
export default {
name: 'HelloWorld',
components: { ComA },
data() {
return {
msg: 'Welcome'
}
},
methods: {
changeA() {
// 获取到子组件A
this.$children[0].messageA = 'this is new value'
}
}
}
</script>
html
// 子组件中
<template>
<div class="com_a">
<span>{{messageA}}</span>
<p>获取父组件的值为: {{parentVal}}</p>
</div>
</template>
<script>
export default {
data() {
return {
messageA: 'this is old'
}
},
computed:{
parentVal(){
return this.$parent.msg;
}
}
}
</script>
要注意边界情况,如在#app上拿$parent得到的是new Vue()的实例,在这实例上再拿$parent得到的是undefined,而在最底层的子组件拿$children是个空数组。也要注意得到$parent和$children的值不一样,$children 的值是数组,而$parent是个对象
ref/refs(vue2)、expose(vue3)
- 在普通DOM元素上使用就指向DOM元素
- 用在子组件上就指向组件实例
vue2(ref,$refs)
html
// 父组件 app.vue
<template>
<component-a ref="comA"></component-a>
</template>
<script>
export default {
mounted () {
const comA = this.$refs.comA;
console.log(comA.name); // Vue.js
comA.sayHello(); // hello
}
}
</script>
html
// 子组件 A.vue
<script>
export default {
data () {
return {
name: 'Vue.js'
}
},
methods: {
sayHello () {
console.log('hello')
}
}
}
</script>
vue3(ref, expose)
html
// 父组件
<template>
<Child ref='childRef'/>
<button @click='getChildData'></button>
</template>
<script setup>
import {ref} from 'vue'
const childRef = ref(null)
const getChildData = () => {
console.log(childRef.value.name) // child
}
</script>
html
// 子组件
<script setup>
import {ref} from 'vue'
const name = ref('child')
defineExpose({
name
})
</script>
兄弟、爷孙(隔代)组件
provide / inject
爷(父)组件通过provide
提供变量,孙(子)组件中通过inject
注入变量
注意: 这里不论子组件嵌套有多深, 只要调用了inject 那么就可以注入provide中的数据,而不局限于只能从当前父组件的props属性中回去数据
vue2
假设有三个组件: A.vue、B.vue、C.vue 其中 C是B的子组件,B是A的子组件
html
// A.vue
<template>
<div>
<comB></comB>
</div>
</template>
<script>
import comB from '../components/test/comB.vue'
export default {
name: "A",
provide: {
for: "demo"
},
components:{
comB
}
}
</script>
html
// B.vue
<template>
<div>
{{demo}}
<comC></comC>
</div>
</template>
<script>
import comC from '../components/test/comC.vue'
export default {
name: "B",
inject: ['for'],
data() {
return {
demo: this.for
}
},
components: {
comC
}
}
</script>
html
// C.vue
<template>
<div>
{{demo}}
</div>
</template>
<script>
export default {
name: "C",
inject: ['for'],
data() {
return {
demo: this.for
}
}
}
</script>
vue3
html
// A.vue
<script setup>
import {provide} from 'vue'
provide('name', 'KESHAOYE')
</script>
html
// A的子组件 B.vue
<script setup>
import {inject} from 'vue'
const name = inject('name')
console.log(name) // KESHAOYE
</script>
html
// A的孙组件、B的子组件 C.vue
<script setup>
import {inject} from 'vue'
const name = inject('name')
console.log(name) // KESHAOYE
</script>
attrs(vue2、vue3)、listeners(vue2)
attrs
可以获取到所有prop
获取不到的attribute(除class和style)
vue2
html
// 父组件
<template>
<Child :msg1='msg1' :msg2='msg2' @test='test'/>
</template>
<script>
import Child from './Child.vue'
export default {
data() {
return {
msg1: 'msg1',
msg2: 'msg2'
}
},
components: {
Child
},
methods: {
test() {
console.log('父组件test方法')
}
}
}
</script>
html
// 子组件
<template>
<button @click='getParentProp'>获取父组件传入的属性/方法</button>
</template>
<script>
export default {
data() {
return {
msg1: 'msg1',
msg2: 'msg2'
}
},
props: {
msg1: {
type: 'string',
default: 'msg1'
}
},
methods: {
getParentProp() {
console.log(this.$attrs,this.$listeners) // [msg2: 'msg2'], [test: test()]
}
}
}
</script>
vue3(attrs)
html
// 父组件
<template>
<Child :msg1='msg1' :msg2='msg2'/>
</template>
<script setup>
import Child from './Child'
import { ref } from 'vue'
const msg1 = ref('msg1')
const msg2 = ref('msg2')
</script>
html
// 子组件
<script setup>
import { useAttrs } from 'vue'
const attrs = useAttrs()
</script>
EventBus(vue2)
js
// 方式一: 创建eventbus.js
import Vue from 'vue'
export const EventBus = new Vue()
// 方式二: 在Vue实例上挂载
// main.js
Vue.prototype.$EventBus = new Vue()
js
// 在需要向外部发送自定义事件的组件内
<template>
<button @click="handlerClick">按钮</button>
</template>
import Bus from "./Bus.js"
export default{
methods:{
handlerClick(){
// 自定义事件名 sendMsg
Bus.$emit("sendMsg", "这是要向外部发送的数据")
}
}
}
// 在需要接收外部事件的组件内
import Bus from "./Bus.js"
export default{
mounted(){
// 监听事件的触发
Bus.$on("sendMsg", data => {
console.log("这是接收到的数据:", data)
})
},
beforeDestroy(){
// 取消监听
Bus.$off("sendMsg")
}
}
vue3(mitt)
由于vue3没有了EventBus跨组件通信,官方推荐使用mitt
代替
首先安装
npm i mitt -s
封装BUS
js// bus.js import mitt from 'mitt' export default mitt()
在组件中引入使用
html// A组件 <script setup> import mitt from './mitt' const handleClick = () => { mitt.emit('handleChange','我发出了通知') } </script>
html// B组件 <script setup> import mitt from './mitt' mitt.on('handleChange', (e)=>{console.log(e)})// 我发出了通知 </script>
vuex/pinia
localStorage/sessionStorage