vue3组件通信父子 子父 兄弟 子孙 组件间通信一站解决

vue3中组件通信方式有以下几种:

  • props
  • custom event 自定义事件
  • event bus 全局事件总线 $bus(mitt)
  • v-model
  • userAttrs
  • ref与$parent
  • provide与inject
  • pinia
  • slot 插槽

现在用具体的Demo来实现组件通信
注:此文章示例基于 vue3+ts+vite

  1. props

    新建Father.vue和Children.vue两个文件
    流程是:父(Father.vue)传子(Children.vue)数据 : name和age
     以下是父(Father.vue)文件的内容
    
<template>
  <!-- 传递非字符串类型需要加v-bind  简写 冒号 -->
  <Children :name="name" age="66" :personal="personal"></Children>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
import Children from './Children.vue';
const name = 'zs';
const personal = reactive([{ bh: '001', xdmc: '寻' }]);
</script>

以下是Children.vue的内容
<template>
  <div>{{ name }}:{{ age }}</div>
</template>
<script setup lang="ts">

// defineProps 是vue3 提供方法,不需要引入直接使用
//defineProps<{ name: string; age: number }>();
const props = defineProps(['name', 'age']);
// 需要使用到defineProps方法去接收父组件传过来的数据。
// props :只读
console.log(props.name);
</script>

  1. custom event 自定义事件

    >在vue框架中事件分为两种:一种是原生的DOM事件,另外一种是自定义事件.
    原生DOM事件可以让用户与网页进行交互,比如:click、change、mouseeter、mouseleave...
    自定义事件可以实现子组件给父组件传递数据。
    

以下是父组件 Father.vue的内容

<template>
  <Children @click="handler"></Children>
</template>
<script setup lang="ts">
import Children from './Children.vue';
// 点击子组件任意位置都能触发
const handler = () => {
  console.log('点击了...');
};
</script>

自定义事件实现子组件给父组件传递数据
利用 defineEmits方法返回函数触发自定义事件
defineEmits方法不需要引入直接使用

以下是Father.vue中的内容

<template>
  <div class="father">
    <Children @setHandler="handler"></Children>
  </div>
</template>
<script setup lang="ts">
import Children from './Children.vue';
// 接收从子组件传过来的值
const handler = (params1: any, params2: any) => {
  console.log(params1, params2);
};
</script>
<style scoped>
.father {
  width: 800px;
  height: 800px;
  background-color: green;
}
</style>

以下是子组件children.vue里的内容

<template>
  <div class="children">
    <el-button @click="setValue">点击我传给父组件值</el-button>
  </div>
</template>
<script setup lang="ts">
const $emit = defineEmits(['setHandler']);
const setValue = () => {
  $emit('setHandler', 'zs', '18');
};
</script>
<style scoped>
.children {
  width: 200px;
  height: 200px;
  z-index: 99;
  background-color: red;
}
</style>

  1. event bus 全局事件总线 $bus(mitt)

1.安装插件 npm install mitt
2.新建 bus.ts文件引入mitt
3.新建文件 father.vue children1.vue children2.vue
4.流程是:兄弟之间传值 children1 传值给children2

 以下是bus文件 bus.ts
import mitt from 'mitt';
const $bus = mitt();
export default $bus;
 以下father.vue文件的内容
<template>
  <div class="father">
    <Children1></Children1>
    <Children2></Children2>
  </div>
</template>
<script setup lang="ts">
import Children1 from './Children1.vue';
import Children2 from './Children2.vue';
</script>
<style scoped>
.father {
  width: 400px;
  height: 400px;
  background-color: green;
}
</style>

 以下children1.vue文件的内容
<template>
  <div class="children">
    <el-button @click="handler">点击送给兄弟值</el-button>
  </div>
</template>
<script setup lang="ts">
import $bus from '../bus/index';
const handler = () => {
  $bus.emit('setValue', '兄弟来吃饭');
};
</script>
<style scoped>
.children {
  width: 200px;
  height: 200px;
  z-index: 99;
  background-color: red;
}
</style>

 以下children2.vue文件的内容
<template>
  <div class="children">{{ childrenValue }}</div>
</template>
<script setup lang="ts">
import $bus from '../bus/index';
import { onMounted, ref } from 'vue';
const childrenValue = ref();
// 组件挂载完毕的时候,当前组件绑定一个事件,接收将来兄弟组件传递的数据。
onMounted(() => {
// 第一个参数:即为事件类型,第二个参数:即为事件回调
  $bus.on('setValue', (item) => {
    childrenValue.value = item;
  });
});
</script>
<style scoped>
.children {
  width: 200px;
  height: 200px;
  z-index: 99;
  color: #ffff;
  background-color: black;
}
</style>

  1. v-model

1.收集表单数据,实现数据双向绑定
2.实现父子组件数据同步
3.v-model在组件身上使用 相当于给子组件props[modelValue=1000];相当于给子组件绑定自定义事件
4.同时绑定多个v-model

 以下father.vue文件的内容
<template>
  <div class="father">
    {{ money }}
    <el-button @click="money += 1">父组件更改mony</el-button>
    <Children1 v-model:money="money" v-model:pageNo='pageNo' v-model:pageSize='pageSize'></Children1>
    <Children2></Children2>
  </div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Children1 from './Children1.vue';
import Children2 from './Children2.vue';
const money = ref(0);
</script>
<style scoped>
.father {
  width: 400px;
  height: 400px;
  background-color: green;
}
</style>

 以下children1.vue文件的内容
<template>
  <div class="children">
    <el-button @click="handler">点击子组件同步父组件的值</el-button>
  </div>
</template>
<script setup lang="ts">
const props = defineProps(['money']);
// 用v-model 绑定 会自动生成 update:money
const $emit = defineEmits(['update:money']);
const handler = () => {
  $emit('update:money', props.money + 100.5);
};
</script>
<style scoped>
.children {
  width: 200px;
  height: 200px;
  z-index: 99;
  background-color: red;
}
</style>

  1. userAttrs

    vue3框架提供了一个方法useAttrs方法,它可以获取到组件身上的属性与事件
    props 与 useAttrs 方法都可以获取父组件传递过来的属性与属性值
    但是props接收了useAttrs方法就获取不到了
    

    以下father.vue文件的内容

<template>
  <div class="father">
    <Children
      type="primary"
      size="small"
      title="编辑按钮"
      @click="onClickGet"
    ></Children>
  </div>
</template>
<script setup lang="ts">
import Children from './Children.vue';
const onClickGet = () => {};
</script>
<style scoped>
.father {
  width: 400px;
  height: 400px;
  background-color: green;
}
</style>

 以下children.vue文件的内容
  <div class="children">{{ $attrs }}</div>
</template>
<script setup lang="ts">
import { useAttrs } from 'vue';
// props 与 useAttrs 方法都可以获取父组件传递过来的属性与属性值
//但是props接收了useAttrs方法就获取不到了
const props = defineProps(['title']);
let $attrs = useAttrs();
console.log($attrs);
</script>
<style scoped>
.children {
  width: 200px;
  height: 200px;
  z-index: 99;
  background-color: red;
}
</style>
  1. ref与$parent

    ref可以获取真实的DOM节点,可以获取到子组件实例
    	组件内部数据对外关闭的,别人不能访问
    	如果想让外部访问需要通过defineExpose方法对外暴露
    $parent 可以在子组件内部获取到父组件的实例
    
以下是father.vue中的内容
<template>
  <div class="father">
    <Children ref="sonRef"></Children>
    <el-button @click="updateSon">点击更改子组件的数据</el-button>
  </div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Children from './Children.vue';
const sonRef = ref();
const updateSon = () => {
  sonRef.value.money = 2020;
};
</script>
<style scoped>
.father {
  width: 400px;
  height: 400px;
  background-color: green;
}
</style>

 以下children.vue文件的内容
<template>
  <div class="children">{{ money }}</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const money = ref(1000);
// 组件内部数据对外关闭的,别人不能访问
// 如果想让外部访问需要通过defineExpose方法对外暴露
defineExpose({ money });
</script>
<style scoped>
.children {
  width: 200px;
  height: 200px;
  z-index: 99;
  background-color: red;
}
</style>

  1. provide与inject

    vue提供了provide(提供)与inject(注入),可以是爱你隔辈组件传递数据
    数据指向同一个对象 所以孙辈修改数据,祖先数也同步更新
    流程:
    	创建ancestors.vue(祖先) father.vue(儿子) son.vue(孙子) 文件
    	祖先给后代 孙子组件传递数据。
    
<template>
  <div class="father">
    <Children ref="sonRef"></Children>
  </div>
</template>
<script setup lang="ts">
import { provide } from 'vue';
import Children from './Children.vue';
// 祖先组件给后代组件提供数据
// 两个参数:第一个参数是提供的数据key
// 第二个参数:祖先组件提供数据
provide('Token', '乖孙儿给你糖吃');
</script>
<style scoped>
.father {
  width: 400px;
  height: 400px;
  background-color: green;
}
</style>

</style>

 以下son.vue文件的内容
<template>
  <div class="children">{{ gr }}:谢谢爷爷</div>
</template>
<script setup lang="ts">
import { inject } from 'vue';
// 后代元素 不论是儿子还是孙子 都可以接收
// 注入祖先组件提供数据
// 需要参数:即为 祖先提供数据的key
const gr = inject('Token');
</script>
<style scoped>
.children {
  width: 200px;
  height: 200px;
  z-index: 99;
  background-color: red;
}
</style>
  1. pinia

    1. vuex 
    	1.1 集中式管理状态容器,可以实现任意组件之间通信
    	2.2 核心概念:state、mutation、actions、getters、moudules
    2.pinia
    	2.1 集中式管理状态容器,可以实现任意组件之间通信
    	2.2 核心概念:state、actions、getters
    	2.3 pinia 选择式API 和组合式API  
    
//以下是pinia选择式API
// 定义 info 小仓库
import { defineStore } from 'pinia';
// 第一个仓库:小仓库名字 第二个参数:小仓库配置对象
// defineStore 方法执行会返回一个函数,函数作用就是让组件可以获取到仓库数据
let userInfoStore = defineStore('info', {
    // 存储数据:state;
    state: () => {
        return {
            count: 99,
            arr:[1,2,3,4,5,6,7,8,9,10]
        }
    },
    actions: {
        // 注意:函数没有context上下文对象
        // 没有commit、没有mutations去修改数据
        updateNum(a:number,b:number) {
            this.count +=a;
        }
    },
    getters: {
        total() {
            let result: any = this.arr.reduce((prev: number, next: number) => {
                return prev + next;
            }, 0);
            return result
        }
    }
});
// 对外暴露方法
export default userInfoStore;
// 组件使用
<div>{{infoStore.count}}</div>
<div>{{infoStore.total}}</div>
import userInforStore from '../../store/modules/info';
// 获取小仓库对象
let infoStore=useInfoStore();
// 修改数据方法
const updateCount=()=>{
    infoStore.updateNum(66,77);
}
// 以下是pinia 组合式API
// 定义组合式API仓库
import { defineStore } from 'pinia';
import { ref } from 'vue';
let useTodoStore = defineStore('todo', () => {
    let todos = ref([{ id: 1, title: '吃饭' }, { id: 2, title: '睡觉' }, { id: 3, title: '打豆豆' }]);
    let arr = ref([1, 2, 3, 4, 5]);
  
    const total = computed(() => {
        return arr.value.reduce((prev:number, next:number) => {
            return prev + next;
        },0)
    })
    return {
        todos,
        arr,
        updateTodo() {
            todos.value.push({id:4,title:'组合式API'})
        }
    }
})
export default useTodoStore;
  1. slot 插槽

    1. 默认插槽
    2. 具名插槽
    3. 作用域插槽
//以下father.vue的内容
<template>
  <div class="father">
    <Children ref="sonRef">
      <h1>我是默认插槽</h1>
    </Children>
  </div>
</template>
<script setup lang="ts"></script>
<style scoped>
.father {
  width: 400px;
  height: 400px;
  background-color: green;
}
</style>
// 以下是children的内容
<template>
  <div class="children">
    <slot></slot>
  </div>
</template>
<script setup lang="ts"></script>
<style scoped>
.children {
  width: 200px;
  height: 200px;
  z-index: 99;
  background-color: red;
}
</style>


以下是具名插槽
// 以下是fater.vue 的内容
<template>
  <div class="father">
    <Children ref="sonRef">
      <template v-slot:header>
        <h1>具名插槽---头部</h1>
      </template>
      <template v-slot:footer>
        <h1>具名插槽---底部</h1>
      </template>
    </Children>
  </div>
</template>
<script setup lang="ts">
import Children from './Children.vue';
</script>
<style scoped>
.father {
  width: 400px;
  height: 400px;
  background-color: green;
}
</style>
// 以下是children.vue 的内容
<template>
  <div class="children">
    <slot name="header"></slot>
    <slot name="footer"></slot>
  </div>
</template>
<script setup lang="ts"></script>
<style scoped>
.children {
  width: 200px;
  height: 200px;
  border: 1px solid red;
  background-color: red;
}
</style>

以下是作用域插槽
	可以传递数据的插槽
	子组件可以将数据回传给父组件
	父组件可以决定这些回传的数据是以何种结构或外观在内部去展示
//这是father.vue的内容
<template>
  <div class="father">
    <Children ref="sonRef" :zcb="arr">
      <template v-slot="$row">
        <div
          :style="{
            background: $row.$index % 2 == 0 ? 'red' : '#fff',
            width: 200,
            height: 20
          }"
        >
          {{ $row.$row.name }}
        </div>
      </template>
    </Children>
  </div>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
import Children from './Children.vue';
let arr = reactive([
  { name: '周一', value: '3顿' },
  { name: '周二', value: '4顿' },
  { name: '周三', value: '2顿' },
  { name: '周四', value: '3顿' },
  { name: '周五', value: '3顿' },
  { name: '周六', value: '3顿' },
  { name: '周日', value: '1顿' }
]);
</script>
<style scoped>
.father {
  width: 400px;
  height: 400px;
  background-color: green;
}
</style>

// 这是children.vue 的内容
<template>
  <div class="children" v-for="(item, index) in zcb" :key="item.name">
    <slot :$row="item" :$index="index"></slot>
  </div>
</template>
<script setup lang="ts">
defineProps(['zcb']);
</script>
<style scoped>
.children {
  width: 20px;
  height: 20px;
  border: 1px solid red;
  background-color: red;
}
</style>

</style>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值