vue3组件间通信

Posted wj-goodgoodstudy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue3组件间通信相关的知识,希望对你有一定的参考价值。

props

  • 父子组件之间通信最好的方式
// 父组件
<template>
  <div class="box">
    <h1>props:这里是父组件</h1>
    <hr />
    <Child :money="money"></Child>
  </div>
</template>

<script setup lang="ts">
//props:可以实现父子组件通信,props数据还是只读的!!!
import Child from "./Child.vue";
import  ref  from "vue";
let money = ref(10000);
</script>

// 子组件
<template>
  <div class="son">
       <h1>我是子组件</h1>
       <p>props.money</p>
      <!--props可以省略前面的名字--->
       <p>money</p>
       <button @click="updateProps">修改props数据</button>
  </div>
</template>

<script setup lang="ts">
//需要使用到defineProps方法去接受父组件传递过来的数据
//defineProps是Vue3提供方法,不需要引入直接使用
let props = defineProps([\'money\']); //数组|对象写法都可以
//按钮点击的回调
const updateProps = ()=>
  // props.money+=10;  props:只读的
  console.log(props)

</script>

自定义事件

  • 父子组件通信
// 父组件
<template>
  <div>
    <button @click="handler1(1,2,3,$event)">点击我传递多个参数</button>
    <hr>
    <!--
        vue2框架当中:这种写法自定义事件,可以通过.native修饰符变为原生DOM事件
        vue3框架下面写法其实即为原生DOM事件
        vue3:原生的DOM事件不管是放在标签身上、组件标签身上都是原生DOM事件
      -->
    <Event1 @click="handler2"></Event1>
    <hr>
    <!-- 绑定自定义事件xxx:实现子组件给父组件传递数据 -->
    <Event2 @xxx="handler3" @click="handler4"></Event2>
  </div>
</template>
<script setup lang="ts">
//引入子组件
import Event1 from \'./Event1.vue\';
//引入子组件
import Event2 from \'./Event2.vue\';
//事件回调--1
const handler = (event)=>
    //event即为事件对象
    console.log(event);

//事件回调--2
const handler1 = (a,b,c,$event)=>
   console.log(a,b,c,$event)

//事件回调---3
const handler2 = ()=>
    console.log(123);

//事件回调---4
const handler3 = (param1,param2)=>
    console.log(param1,param2);

</script>

// 子组件2
<template>
  <div class="child">
    <p>我是子组件2</p>
    <button @click="handler">点击我触发自定义事件xxx</button>
    <button @click="$emit(\'click\',\'111\',\'222\')">点击我触发自定义事件click</button>
  </div>
</template>

<script setup lang="ts">
//利用defineEmits方法返回函数触发自定义事件
//defineEmits方法不需要引入直接使用
let $emit = defineEmits([\'xxx\',\'click\']);
//按钮点击回调
const handler = () => 
  //第一个参数:事件类型 第二个|三个|N参数即为注入数据
    $emit(\'xxx\',\'333\',\'444\');
;
</script>

全局事件总线

  • 任意组件间通信
// 使用mitt,先封装一个$bus
// bus
//引入mitt插件:mitt一个方法,方法执行会返回bus对象
import mitt from \'mitt\';
const $bus = mitt();
export default $bus;

// 组件1
<template>
  <div class="child1">
    <h3>我是子组件child1</h3>
  </div>
</template>

<script setup lang="ts">
import $bus from "../../bus";
//组合式API函数
import  onMounted  from "vue";
//组件挂载完毕的时候,当前组件绑定一个事件,接受将来兄弟组件传递的数据
onMounted(() => 
  //第一个参数:即为事件类型  第二个参数:即为事件回调
  $bus.on("child1", (str) => 
    console.log(str);
  );
);
</script>

// 组件2
<template>
  <div class="child2">
     <h2>我是子组件child2</h2>
     <button @click="handler">点击给child1传值</button>
  </div>
</template>

<script setup lang="ts">
//引入$bus对象
import $bus from \'../../bus\';
//点击按钮回调
const handler = ()=>
  $bus.emit(\'child1\',"你好");

</script>

v-model

  • 本质上还是使用了自定义事件
// v-model原理
// 在原生元素上
<input v-model="text" />

// 等价展开
<input
  :value="text"
  @input="text = $event.target.value"
/>

// 在组件上展开
<Child1
  :modelValue="text"
  @update:modelValue="newValue => text = newValue"
/>

// Child1
<template>
  <input
    :value="modelValue"
    @input="$emit(\'update:modelValue\', $event.target.value)"
  />
</template>

<script setup>
    // 这里只能使用modleValue和upate:modleValue
    defineProps([\'modelValue\'])
    defineEmits([\'update:modelValue\'])
</script>

// 指定参数名称
<Child2 v-model:text="text" />

// Child2
<script setup>
    defineProps([\'text\'])
    defineEmits([\'update:text\'])
</script>

// 实际上指定参数名称就是在子组件的defineEmits([\'update:指定参数\'])、defineProps([指定参数])

useAttrs

  • 接收父组件的属性和方法,多用于组件二次封装
// 父组件
<template>
  <div>
    <h1>useAttrs</h1>
    <!-- 自定义组件 -->
    <HintButton type="primary" size="small" :icon="Edit" title="编辑按钮" @click="handler" ></HintButton>
  </div>
</template>

// 子组件
<template>
  <div :title="title">
    <el-button :="$attrs"></el-button>
  </div>
</template>
<script setup lang="ts">
//引入useAttrs方法:获取组件标签身上属性与事件
import  useAttrs  from "vue";
//此方法执行会返回一个对象,里面是传过来的一堆属性与方法
let $attrs = useAttrs();

//万一用props接受title
let props = defineProps(["title"]);
//props与useAttrs方法都可以获取父组件传递过来的属性与属性值
//但是props接受了useAttrs方法就获取不到了
console.log($attrs);
</script>

ref和$parent

  • ref 可以获取真实的DOM节点,可以获取到子组件实例VC
  • $parent 可以在子组件内部获取到父组件的实例
  • 只能获取通过 defineExpose 暴露出来的对象
// 父组件
<template>
  <div class="box">
    <h1>我是父亲曹操:money</h1>
    <button @click="handler">找我的儿子曹植借10元</button>
    <hr>
    <Son ref="son"></Son>
    <hr>
    <Dau></Dau>
  </div>
</template>

<script setup lang="ts">
//ref:可以获取真实的DOM节点,可以获取到子组件实例VC
//$parent:可以在子组件内部获取到父组件的实例
//引入子组件
import Son from \'./Son.vue\'
import Dau from \'./Daughter.vue\'
import ref from \'vue\';
//父组件钱数
let money = ref(100000000);
//获取子组件的实例
let son = ref();
//父组件内部按钮点击回调
const handler = ()=>
   money.value+=10;
   //儿子钱数减去10
   son.value.money-=10;
   son.value.fly();

//对外暴露
defineExpose(
   money
)
</script>

// 子组件1
<template>
  <div class="son">
    <h3>我是子组件:曹植money</h3>
  </div>
</template>

<script setup lang="ts">
import ref from \'vue\';
//儿子钱数
let money = ref(666);
const fly = ()=>
  console.log(\'我可以飞\');

//组件内部数据对外关闭的,别人不能访问
//如果想让外部访问需要通过defineExpose方法对外暴露
defineExpose(
  money,
  fly
)
</script>

// 子组件2
<template>
  <div class="dau">
     <h1>我是闺女曹杰money</h1>
     <button @click="handler($parent)">点击我爸爸给我10000元</button>
  </div>
</template>

<script setup lang="ts">
import ref from \'vue\';
//闺女钱数
let money = ref(999999);
//闺女按钮点击回调
const handler = ($parent)=>
   money.value+=10000;
   $parent.money-=10000;

</script>

provide 和 inject

  • 与后代组件通信,provide 注入数据,inject 接收数据
// 祖先组件
<script setup lang="ts">
import Child from "./Child.vue";
//vue3提供provide(提供)与inject(注入),可以实现隔辈组件传递数据
import  ref, provide  from "vue";
let car = ref("法拉利");
//祖先组件给后代组件提供数据
//两个参数:第一个参数就是提供的数据key
//第二个参数:祖先组件提供数据
provide("TOKEN", car);
</script>

// 孙子组件
<script setup lang="ts">
import inject from \'vue\';
//注入祖先组件提供数据
//需要参数:即为祖先提供数据的key
let car = inject(\'TOKEN\');
const updateCar = ()=>
   car.value  = \'自行车\';

</script>

pinia

  • vuex 升级版,任意组件间通信

pinia 只有三个核心概念

  • state,存放数据和状态
  • actions,存放数据和状态处理的方法
  • getters,类似于计算属性

store/index.ts

// 创建 pinia 仓库,在 src 根目录下创建 store 文件夹,在 store 中创建 modules 文件夹存放小仓库的数据,在 store 中创建 index.ts 文件进行大仓库的创建
// index.ts
//创建大仓库
import  createPinia  from \'pinia\';
//createPinia方法可以用于创建大仓库
let store = createPinia();
//对外暴露,安装仓库
export default store;

store/modules/info.ts

//定义info小仓库
import  defineStore  from "pinia";
//第一个仓库:小仓库名字  第二个参数:小仓库配置对象
//defineStore方法执行会返回一个函数,函数作用就是让组件可以获取到仓库数据
let useInfoStore = 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 useInfoStore;

store/modules/todo.ts

//定义组合式API仓库,有点类似于自定义 hook 的感觉了
import  defineStore  from "pinia";
import  ref, computed,watch 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, next) => 
            return prev + next;
        , 0)
    )
    //务必要返回一个对象:属性与方法可以提供给组件使用
    return 
        todos,
        arr,
        total,
        updateTodo() 
            todos.value.push( id: 4, title: \'组合式API方法\' );
        
    
);

export default useTodoStore;

组件1

<template>
  <div class="child">
    // 函数.属性获取store的属性值
    <h1> infoStore.count ---infoStore.total</h1>
    <button @click="updateCount">点击我修改仓库数据</button>
  </div>
</template>

<script setup lang="ts">
import useInfoStore from "../../store/modules/info";
//获取小仓库对象,defineStore 返回的是一个函数,所以需要调用函数的方式来调用 store
let infoStore = useInfoStore();
console.log(infoStore);
//修改数据方法
const updateCount = () => 
  //仓库调用自身的方法去修改仓库的数据,函数.方法调用方法
  infoStore.updateNum(66,77);
;
</script>

插槽

  • 也是父子组件通信的方式
// 组件1
    <Test>
      <div>
        <pre>默认</pre>
      </div>
      <!-- 具名插槽填充a -->
      <template #a>
        <div>我是填充具名插槽a位置结构</div>
      </template>
      <!-- 具名插槽填充b v-slot指令可以简化为# -->
      <template #b>
        <div>我是填充具名插槽b位置结构</div>
      </template>
    </Test>
// 默认插槽和具名插槽
<template>
  <div class="box">
    <h1>我是子组件默认插槽</h1>
    <!-- 默认插槽 -->
    <slot></slot>
    <h1>我是子组件默认插槽</h1>
    <h1>具名插槽填充数据</h1>
    <slot name="a"></slot>
    <h1>具名插槽填充数据</h1>
    <h1>具名插槽填充数据</h1>
    <slot name="b"></slot>
    <h1>具名插槽填充数据</h1>
  </div>
</template>
// 组件2
// 根据子组件传过来的值 done 来决定展示的颜色
    <Test1 :todos="todos">
      <template v-slot=" $row, $index ">
        <p :>
           $row.title -- $index 
        </p>
      </template>
    </Test1>
// 作用域插槽
<template>
  <div class="box">
    <h1>作用域插槽</h1>
    <ul>
      <li v-for="(item, index) in todos" :key="item.id">
        <!--作用域插槽:可以讲数据回传给父组件-->
        <slot :$row="item" :$index="index"></slot>
      </li>
    </ul>
  </div>
</template>

<script setup lang="ts">
//通过props接受父组件传递数据
defineProps(["todos"]);
</script>

Vue 组件间的通信

Vue 组件间的通信

组件间通信方式

  1. props :父组件向子组件传递数据。
  2. $emit :自定义事件。
  3. slot :插槽分发内容,带上标签数据一起传输,主要是父组件想子组件传输。

组件间通信规则

  1. 不要在子组件中直接修改父组件传递的数据。
  2. 数据初始化的时候,应当看初始化的数据是否用于多个组件中,如果需要被用于多个组件中,则初始化在父组件中;如果只在一个组件中使用,那就初始化在这个要使用的组件中。
  3. 数据初始化在哪个组件,更新数据的方法(函数)就应该定义在哪个组件中。

以上是关于vue3组件间通信的主要内容,如果未能解决你的问题,请参考以下文章

「 VUE3 + TS + Vite 」父子组件间如何通信?

vue3.x组件间通信,实用小技巧都在这里

vue3provide和inject实现跨层级组件间通信祖孙组件

关于vue2与vue3

Vue3 父子组件通信

Vue3 组件通信学习笔记