vue3+ts组件通信

📝 VUE3组件通信

1️⃣ props父子组件通信

props特性:

  1. **直接修改props:**Vue中的props是单向数据流,父组件传递给子组件的props应该被视为只读数据。不可以直接修改props中的数据,会导致应用的数据流变得难以追踪和理解。如果需要在子组件中修改传递的值,应该将其复制到组件内部的data或computed属性中进行修改
  2. **非响应式数据:**当props是基本类型(例如字符串、数字、布尔值等)时,修改其值不会影响到父组件中的数据。但如果props是对象或数组等引用类型数据,直接在子组件中修改这些引用类型的值会影响到父组件的数据,这可能导致意外的行为和bug。最好的做法是避免在子组件中直接修改这些引用类型的props,而是通过触发事件或调用父组件传递的函数来修改
  3. **命名冲突:**当命名多个props时,要注意避免命名冲突。尤其是在大型应用中,可能存在多个组件嵌套的情况,如果不注意命名规范,可能会导致props命名重复,造成混淆和错误。
  4. 合理使用v-model:在子组件中使用**v-model时,要确保v-model的值是通过model选项或名为value的prop传递,并且要正确处理input事件。在自定义组件中正确实现v-model**功能可以提高组件的可重用性和易用性。
  5. 传递大量数据:不应该通过props传递大量的数据,特别是当这些数据需要在多个组件之间传递时。这样做可能会导致性能下降和难以维护的代码。大批量的数据可以使用状态管理的方式进行传递,后面会总结到pinia状态管理方案来替代Vuex

props组件通信代码示例:

父组件:parentComponents.vue

<template>
  <div class="box">
    <h1>props:我是父组件曹操</h1>
    <hr />
    <Child info="我是曹操" :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>

子组件:childComponents.vue**(defineProps方法是vue3提供的方法,直接使用不需要引入)**

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

<script setup lang="ts">
//需要使用到defineProps方法去接受父组件传递过来的数据
let props = defineProps(['info','money']); //数组|对象写法都可以
//按钮点击的回调
const updateProps = ()=>{
  console.log(props.info)
}
</script>

2️⃣ 自定义事件传值

自定义事件注意事项:

  1. 使用适当的命名规范:确保事件名称清晰明了,并且不会与现有的DOM事件名称冲突。
  2. 避免命名冲突:如果应用中有多个事件,确保它们的命名不会产生冲突。
  3. 组件解耦:自定义事件适用于解耦组件,不要过度使用,过度使用它们可能导致代码难以理解和维护。

修饰符操作

  1. .once 修饰符:可以使用 .once 修饰符,确保事件只被触发一次。
  2. .stop 修饰符:阻止事件继续传播到父元素。
  3. .prevent 修饰符:调用事件时调用 event.preventDefault(),防止默认行为。
  4. .capture 修饰符:事件捕获阶段触发处理程序。

自定义事件传值代码示例:

父组件:parentComponents.vue

<template>
  <div>
    <h1>事件</h1>
    <!-- 原生DOM事件 -->
    <pre @click="handler">
      大江东去浪淘尽,千古分流人物
    </pre>
    <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:object) => {
  //event即为事件对象
  console.log(event);
};
//事件回调--2
const handler1 = (a:number, b:number, c:number, $event:object) => {
  console.log(a, b, c, $event);
};
//事件回调---3
const handler2 = () => {
  console.log(123);
};
//事件回调---4
const handler3 = (param1:any, param2:any) => {
  console.log(param1, param2);
};
//事件回调--5
const handler4 = (param1:any, param2:any) => {
  console.log(param1, param2);
};
</script>

子组件1:Event1.vue**(此组件可直接执行父组件方法,未传值)**

<template>
  <div class="son">
      <p>我是子组件1</p>
      <button>点击我也执行</button>
  </div>
</template>

<script setup lang="ts">
</script>

子组件2: Event2.vue (defineEmits和props中的defineProps一样的性质,都是官方提供的方法,可不用引入直接使用)

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

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

3️⃣ 全局事件总线$bus

$bus封装与使用方法

$bus需要借助一个mitt的npm官方库,首先在项目中引入mitt,mitt官网,随后在项目中使用mitt方法创建一个$bus对象并挂载到vue实例:

$bus封装代码

//引入mitt插件:mitt一个方法,方法执行会返回bus对象
import mitt from 'mitt';
const $bus = mitt();
export default $bus;

$bus组件使用方法代码示例

父组件:parentComponents.vue

<template>
  <div class="box">
    <h1>全局事件总线$bus</h1>
    <hr />
    <div class="container">
      <Child1></Child1>
      <Child2></Child2>
    </div>
  </div>
</template>

<script setup lang="ts">
//引入子组件
import Child1 from "./Child1.vue";
import Child2 from "./Child2.vue";
</script>

子组件1: Child1.vue

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

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

子组件2: Child.vue

<template>
  <div class="child2">
     <h2>我是子组件2:曹丕</h2>
     <button @click="handler">点击我给兄弟送一台法拉利</button>
  </div>
</template>

<script setup lang="ts">
//引入$bus对象
import $bus from '../../bus';
//点击按钮回调
const handler = ()=>{
  $bus.emit('car',{car:"法拉利"});
}
</script>

4️⃣ 使用v-model进行父子组件通信

v-model传值注意事项

  1. 正确实现v-model:在子组件中,要接受 valueemit('update:modelValue', newValue) 来正确实现 v-model 功能。
  2. 维护v-model的值:确保在子组件内部修改值时,不要直接修改 props 的值。应该在组件内部通过 emit 触发 update:modelValue 事件来通知父组件修改值。
  3. v-model修饰符v-model 支持一些修饰符,如 .lazy.trim,它们可以改变数据同步的时机或者处理输入的值。在需要的情况下可以使用这些修饰符。

v-model传值代码示例:

父组件:parentComponents.vue

<template>
  <div>
    <h1>v-model:钱数{{ money }}{{ pageNo }}{{ pageSize }}</h1>
    <input type="text" v-model="info" />
    <hr />
    <!-- props:父亲给儿子数据 -->
    <!-- <Child :modelValue="money" @update:modelValue="handler"></Child> -->
    <!--
       v-model组件身上使用
       第一:相当有给子组件传递props[modelValue] = 10000
       第二:相当于给子组件绑定自定义事件update:modelValue
     -->
    <Child v-model="money"></Child>
    <hr />
    <Child1 v-model:pageNo="pageNo" v-model:pageSize="pageSize"></Child1>
  </div>
</template>

<script setup lang="ts">
//v-model指令:收集表单数据,数据双向绑定
//v-model也可以实现组件之间的通信,实现父子组件数据同步的业务
//父亲给子组件数据 props
//子组件给父组件数据 自定义事件
//引入子组件
import Child from "./Child.vue";
import Child1 from "./Child1.vue";
import { ref } from "vue";
let info = ref("");
//父组件的数据钱数
let money = ref(10000);
//自定义事件的回调
const handler = (num: number) => {
  //将来接受子组件传递过来的数据
  money.value = num;
};

//父亲的数据
let pageNo = ref(1);
let pageSize = ref(3);
</script>

子组件1: Child.vue

<template>
  <div class="child">
    <h3>钱数:{{ modelValue }}</h3>
    <button @click="handler">父子组件数据同步</button>
  </div>
</template>

<script setup lang="ts">
//接受props
let props = defineProps(["modelValue"]);
let $emit = defineEmits(['update:modelValue']);
//子组件内部按钮的点击回调
const handler = ()=>{
   //触发自定义事件
   $emit('update:modelValue',props.modelValue+1000);
}
</script>

子组件2:Child1.vue**(以分页参数为例,演示通过v-model传递多个数据)**

<template>
  <div class="child2">
    <h1>同时绑定多个v-model</h1>
    <button @click="handler">pageNo{{ pageNo }}</button>
    <button @click="$emit('update:pageSize', pageSize + 4)">
      pageSize{{ pageSize }}
    </button>
  </div>
</template>

<script setup lang="ts">
let props = defineProps(["pageNo", "pageSize"]);
let $emit = defineEmits(["update:pageNo", "update:pageSize"]);
//第一个按钮的事件回调
const handler = () => {
  $emit("update:pageNo", props.pageNo + 3);
};
</script>

5️⃣ $attrs组件通信

$attrs使用注意事项

  1. 未声明为 Prop 的属性: $attrs 包含的是父组件传递给子组件的但子组件没有声明为 prop 的所有属性。这些属性在子组件中可以被访问到,但是子组件并未明确声明它们,因此要格外小心对这些属性的使用和处理。
  2. 属性验证和处理: 子组件可以使用 $attrs 获取到父组件传递的属性,但这些属性并没有经过子组件的验证。因此,在使用这些属性之前,最好进行必要的验证、类型检查和处理,以确保数据的正确性和安全性。
  3. 避免冲突: 如果子组件的 prop 和 $attrs 中的属性名相同,Vue 3 的默认行为是优先使用 prop 中的属性。这可能会导致命名冲突或意外行为。因此,在设计组件时,要注意避免使用相同的属性名作为 prop 和 $attrs 中的未声明 prop。
  4. 透传事件监听器: Vue 3 中,使用 v-on="$listeners" 可以将父组件的事件监听器传递给子组件。这在需要在子组件中监听相同事件时非常有用。这些事件监听器也可以在子组件中通过 $attrs 访问到。
  5. 限制传递属性: 如果需要限制 $attrs 中的属性,可以通过设置 inheritAttrs: false 来阻止未声明的属性传递给子组件。然后,通过 v-bind="$attrs" 手动传递属性到子组件。
  6. 文档和注释: 在子组件中,最好通过文档、注释或者明确的命名,清楚地指出哪些属性来自于 $attrs,以便其他开发者更好地理解和维护代码。

$attrs使用代码示例

父组件:parentComponents.vue (这种通信方式提到了一种V3自带API useAttrs 子组件代码对其有所解释)

<template>
  <div>
    <h1>useAttrs</h1>
    <el-button type="primary" size="small" :icon="Edit"></el-button>
    <!-- 自定义组件 -->
    <HintButton
      type="primary"
      size="small"
      :icon="Edit"
      title="编辑按钮"
      @click="handler"
      @xxx="handler"></HintButton>
  </div>
</template>

<script setup lang="ts">
//vue3框架提供一个方法useAttrs方法,它可以获取组件身上的属性与事件!!!
import HintButton from "./HintButton.vue";
//按钮点击的回调
const handler = () => {
  alert(12306);
};
</script>

子组件: HintButton.vue

<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>

6️⃣ 使用ref进行父子组件通信

ref使用规范

  1. Vue 3 Composition APIref 是 Vue 3 Composition API 的一部分,它允许你在组件中创建响应式的数据。通过 ref 创建的变量是响应式的,可以在模板和组件逻辑中使用。
  2. 创建和访问 ref 变量:要创建一个 ref 变量,你可以使用 ref() 函数。为了访问 ref 变量的值,你需要使用 .value。在模板中使用 ref 变量时,不需要 .value
  3. 注意 ref 变量的响应性ref 变量本身是响应式的,但是如果将 ref 直接赋值给普通的变量或对象,那么对普通变量的更改不会触发组件的重新渲染。如果需要在模板中更新视图,应该使用 ref.value 进行更改。
  4. 异步更新 ref:如果要在异步操作中更改 ref 的值,需要注意 Vue 3 中提供的 nextTick 方法来确保更新发生在视图更新周期之后。
  5. 避免直接修改 ref 对象:直接修改 ref 对象可能导致一些意料之外的问题。应该通过 .value 进行修改和访问。

ref通信代码示例

父组件:parentComponents.vue

  • ref:可以获取真实的DOM节点,可以获取到子组件实例VC
  • $parent:可以在子组件内部获取到父组件的实例
<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">
//引入子组件
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: Son.vue**(defineExpose)**

  • 组件内部数据对外关闭的,别人不能访问
  • 如果想让外部访问需要通过defineExpose方法对外暴露
<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: Daughter.vue

<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:any)=>{
   money.value+=10000;
   $parent.money-=10000;
}
</script>

7️⃣ Provide与Inject跨层组件通信

Provide与Inject说明

Provide:provide 用于在父级组件中提供数据,使其在子组件中可用。)

  1. 提供数据:通过在父组件中使用 provide 提供数据给子组件
  2. 全局 Provide:可以在根组件中使用 provide 来提供全局数据,让所有后代组件都能够访问。

Inject:inject 用于从父级组件或祖先组件中注入数据)

  1. 注入数据:在子组件中使用 inject 从父组件或祖先组件中注入数据。
  2. 提供默认值:可以在 inject 中提供默认值,以防注入的数据未在父组件中提供。

Provide与Inject规范:

  1. 慎用全局 provide:避免滥用全局 provide,因为它可能会导致组件之间的紧密耦合。更好的做法是在适当的情况下,仅在需要时在局部范围内使用 provideinject
  2. 适度使用provideinject 应该用于在特定场景下解决组件通信的问题,而不是被用作全局状态管理器。对于跨组件通信,Vuex 或其他专门的状态管理工具可能更合适。
  3. 文档和约定:为了维护代码的可读性和可维护性,应该在代码中明确地注明哪些数据通过 provide 提供,以及哪些数据通过 inject 注入。
  4. 依赖注入的注意事项:当使用 inject 时,确保了解数据的来源。它不仅可以从直接的父组件中注入,还可以从更上层的祖先组件中提供的数据。

Provide与Inject代码示例:

父组件:ParentComponents.vue

<template>
  <div class="box">
    <h1>Provide与Inject{{car}}</h1>
    <hr />
    <Child></Child>
  </div>
</template>

<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>

子组件(二级组件): Child.vue

<template>
  <div class="child">
     <h1>我是子组件1</h1>
     <Child></Child>
  </div>
</template>

<script setup lang="ts">
import Child from './GrandChild.vue';
</script>

重子组件(三级组件): GrandChild.vue

<template>
  <div class="child1">
    <h1>孙子组件</h1>
    <p>{{car}}</p>
    <button @click="updateCar">更新数据</button>
  </div>
</template>

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

8️⃣ Pinia状态管理方案 Pinia国内官网

9️⃣ 使用插槽进行组件通信

插槽解释:

插槽是 Vue 中一种非常强大的机制,可用于实现组件间的通信和数据传递。通过插槽,父组件可以向子组件传递内容,而子组件可以决定如何展示这些内容。以下是使用插槽进行组件通信的说明以及一些规范:

插槽基本用法

  1. 父组件向子组件传递内容:父组件中的内容可以被传递到子组件中,子组件可以通过插槽(slot)接收并渲染这些内容。
<!-- ParentComponent.vue -->
<template>
  <ChildComponent>
    <!-- 父组件中的内容通过插槽传递给子组件 -->
    <p>Content passed to child component</p>
  </ChildComponent>
</template>
<!-- ChildComponent.vue -->
<template>
  <div>
    <!-- 子组件中使用插槽渲染父组件传递的内容 -->
    <slot></slot>
  </div>
</template>

  1. 具名插槽:除了默认插槽外,还可以使用具名插槽来传递特定名称的内容。
<!-- ParentComponent.vue -->
<template>
  <ChildComponent>
    <!-- 使用具名插槽传递不同名称的内容 -->
    <template v-slot:header>
      <h1>Header content for child component</h1>
    </template>
    <template v-slot:footer>
      <p>Footer content for child component</p>
    </template>
  </ChildComponent>
</template>
<!-- ChildComponent.vue -->
<template>
  <div>
    <header>
      <!-- 渲染具名插槽的内容 -->
      <slot name="header"></slot>
    </header>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

规范和最佳实践

  1. 语义化插槽名称:为插槽使用有意义的名称,使得组件使用者更容易理解和操作。这有助于代码的可维护性。
  2. 适当使用具名插槽:对于复杂的组件,合理使用具名插槽可以更清晰地分离内容,提高组件的可复用性。
  3. 提供默认内容:为插槽提供默认内容,以防止未提供内容时组件渲染出错
<!-- ChildComponent.vue -->
<template>
  <div>
    <!-- 提供默认内容 -->
    <slot>
      <p>Default content if nothing provided</p>
    </slot>
  </div>
</template>
  1. 插槽作用域:插槽内容可能需要访问子组件的数据或方法。在子组件中使用作用域插槽,将数据和方法传递给插槽内容。
<!-- ChildComponent.vue -->
<template>
  <div>
    <slot :data="myData" :method="myMethod"></slot>
  </div>
</template>

<script>
export default {
  data() {
    return {
      myData: 'Some data',
    };
  },
  methods: {
    myMethod() {
      // Some method logic
    },
  },
};
</script>
<!-- ParentComponent.vue -->
<template>
  <ChildComponent>
    <!-- 插槽作用域 -->
    <template v-slot="{ data, method }">
      <p>{{ data }}</p>
      <button @click="method">Click me</button>
    </template>
  </ChildComponent>
</template>

🤗 总结归纳

本文章篇幅较长,方法较多,常用的无非久那几种,要看情况根据自身业务需求选择更适合自己的实现思路和方法,第八条Pinia严格说不是组件通信,而是同Vuex一样的,状态管理库,我会再更新另一篇文章单独说Pinia的使用和Vuex两者之间的区别

📎 与本篇联动文章

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值