【组件通信】props、emit、EvenBus、parent与root、attrs与listeners、Provide与inject、Vuex、pinia

82 阅读4分钟

主要内容

1、 props(父传子)

2、$emit触发自定义事件(子传父)(向上通信)

3、ref(索引,调用子组件的属性和方法,不擅长传递数据。在dom元素时,常用于选择器)

4、 EvenBus(兄弟之间传递)

5、parent与root(共同祖辈)

6、attrs与listeners(祖先传子孙)

7、Provide与inject(Provide组件)

8、Vuex状态管理(vue2使用)

9、pinia状态管理(vue3使用)

子传递父

一、props(父传子)

1、传递:父组件传递数据给子组件

2、使用

<template>  
    <div>  
        <div style="color: pink;">子组件</div>  
        <p>{{ message }}</p>  
        <p>{{ name }}</p>  
        <p>{{ age }}</p>  
    </div>  
</template>  
  
<script setup lang="ts">  
import { defineProps } from 'vue';  
defineProps<{  
message: string;  
name: string;  
age: number;  
}>();  
</script>  
  
<style scoped>  
</style>
<template>  
    <div>  
        <div style="color: skyblue;">Prop 父组件</div>  
        <PropsSon :message="parentMessage" :name="parentName" :age="parentAge" />  
    </div>  
</template>  
<script setup lang="ts">  
import PropsSon from './son.vue';  
  
const parentMessage = 'Hello from Father!';  
const parentName = 'Amy';  
const parentAge = 18;  
</script>  

二、$emit触发自定义事件(子传父)(向上通信)

1、传递:子组件传递数据给父组件

2、使用

<template>  
    <div>  
        <div style="color: skyblue;">父组件</div>  
        <p>来自子组件的消息: {{ message }}</p> 
        <ChildComponent @update:message="handleMessage" />  
    </div>  
</template>  
  
<script setup lang='ts'>  
import { ref } from 'vue';  
import ChildComponent from './son.vue';  
  
const message = ref('');
  
function handleMessage(data: string) {  
    message.value = data;  
}  
</script>
<template>  
    <div style="color: pink;">子组件</div>  
    <button @click="notifyParent">通知父组件</button>  
</template>  
  
<script setup lang='ts'>  
import { defineEmits } from 'vue';  
  
const emit = defineEmits(['update:message']);
  
function notifyParent() {  
    emit('update:message', 'Hello from child'); 
}  
</script>

三、ref(索引,调用子组件的属性和方法,不擅长传递数据。在dom元素时,常用于选择器)

1、ref 本身并不是用来传递数据的。

在 Vue 中,数据通常通过 props 从父组件传递到子组件,通过自定义事件 ($emit 或在 <script setup> 中直接使用 emit 函数) 从子组件传递回父组件。

然而,ref 可以被用来在组件之间共享响应式数据(尽管这通常不是推荐的做法,因为它增加了组件之间的耦合度)

  1. 响应式数据:在组合式 API (<script setup>) 中,ref 通常用于创建一个响应式的引用对象,该对象内部包含一个值,并且当这个值改变时,视图会自动更新。例如:

    	<script setup>  
    	import { ref } from 'vue';  
    	const count = ref(0);
    	</script>  
    	<template>  
    	  <p>{{ count }}</p> 
    	</template>
    
  2. 访问 DOM 元素:当 ref 被用作模板引用(即在模板中通过 ref="someRef" 绑定到元素上)时,你可以在 <script setup> 中通过相同的引用名(需要加上 .value 来访问 DOM 元素,但在模板或计算属性/侦听器中不需要)来获取到这个 DOM 元素。但是,这并不是 ref 的主要目的,而是它的一个额外功能。 \

  3. 访问子组件实例:类似于访问 DOM 元素,当你将 ref 绑定到一个子组件上时,你可以在父组件中通过该引用访问子组件的实例。这允许你调用子组件的方法或访问其属性。但是,这通常不是推荐的做法,因为它增加了组件之间的耦合度。

<template>  
    <div>  
      <button @click="notifyParent">通知父组件</button>  
    </div>  
  </template>  
  <script setup lang="ts">  
  import { defineEmits } from 'vue';  
  const emit = defineEmits(['update:message']);  
  function notifyParent() {  
    emit('update:message', 'Hello from child');  
  }  
  </script>
<template>  
    <div>  
      <p>来自子组件的消息: {{ message }}</p>  
      <ChildComponent @update:message="handleMessage" />  
    </div>  
  </template>  
  <script setup lang="ts">  
  import { ref } from 'vue';  
  import ChildComponent from './son.vue';  
  const message = ref('');  
  function handleMessage(data: string) {  
    message.value = data;  
  }  
  </script>

四、EvenBus(兄弟之间传递)

1、发送者、接收者、传递数据

export class EventBus {  
  private callbacks: Record<string, Array<(...args: any[]) => void>> = {};  
static $emit: any;
  
  $on(event: string, callback: (...args: any[]) => void) {  
    if (!this.callbacks[event]) {  
      this.callbacks[event] = [];  
    }  
    this.callbacks[event].push(callback);  
  }  
  
  $emit(event: string, ...args: any[]) {  
    if (this.callbacks[event]) {  
      this.callbacks[event].forEach(callback => callback(...args));  
    }  
  }  
  
  $off(event: string, callback: (...args: any[]) => void) {  
    if (this.callbacks[event]) {  
      this.callbacks[event] = this.callbacks[event].filter(cb => cb !== callback);  
    }  
  }  
}  
  
export const eventBus = new EventBus();  
<template>  
  <button @click="sendMessage">Send Message</button>  
</template>  
  
<script setup lang="ts">  
import { eventBus } from './eventBus.ts';  
  
const sendMessage = () => {  
  eventBus.$emit('message-sent', 'Hello from Sender');  
};  
</script>
<template>  
  <div>  
    <p>Received Message: {{ message }}</p>  
  </div>  
</template>  
  
<script setup lang="ts">  
import { ref, onMounted } from 'vue';  
import { eventBus } from './eventBus.ts';  
  
const message = ref('');  
  
onMounted(() => {  
  eventBus.$on('message-sent', (msg) => {  
    message.value = msg;  
  });  
});  
</script>

注意:在 Vue 组件中通常不建议使用全局事件总线(EventBus)来传递数据或事件,因为这可能会导致组件之间的紧密耦合和难以追踪的依赖关系。在 Vue 3 中,你可以考虑使用 Vue 3 的 Composition API 提供的 provide 和 inject 功能,或者 Vuex、Vue 3 的响应式状态管理库 Pinia 等更现代的状态管理解决方案来管理跨组件的状态和事件。

五、parent与root(共同祖辈)

parent 和 root(或共同祖辈的概念,更准确地说是根组件)通常是通过组件的层级结构来体现的。

<template>  
  <div>  
    <h1>我是父组件</h1>  
    <p>来自子组件的消息:{{ messageData }}</p>  
    <Child :parentMessage="messageFromParent" @childEvent="handleChildEvent" /> 
     
  </div>  
</template>  
  
<script setup lang="ts">  
import { ref } from 'vue';  
import Child from './Child.vue';  
  
const messageFromParent = ref('来自父组件的消息');  
  
const messageData = ref('')
function handleChildEvent(message: string) {  
  messageData.value = message
}  
</script>
<template>  
    <div>  
      <h2>我是子组件</h2>  
      <p>{{ parentMessage }}</p>  
      <button @click="sendMessageToParent">发送消息给父组件</button>  
    </div>  
  </template>  
    
  <script setup lang="ts">  
  import { defineProps, defineEmits } from 'vue';  
    
  defineProps<{  
    parentMessage: string;  
  }>();  
    
  const emit = defineEmits<{  
    (e: 'childEvent', message: string): void;  
  }>();  
    
  function sendMessageToParent() {  
    emit('childEvent', '来自子组件的消息');  
  }  
  </script>

Root Component:在Vue3中,根组件通常通过createApp(App)挂载的那个组件(比如App.vue)

import { createApp } from 'vue'
import App from './App.vue'
import { router } from '@/routers'
createApp(App).use(router).mount('#app')

六、attrs与listeners(祖先传子孙)

<template>  
    <div>  
      <div style="color: orange;">祖先</div>  
      <p>来自子孙组件的消息:{{ Childdata }}</p>
      <father :grandchild-message="grandchildMessage" @child-event="handleChildEvent" />  
    </div>  
  </template>  
    
  <script setup lang="ts">  
  import { ref } from 'vue';  
  import Father from './father.vue';
    
  const grandchildMessage = ref('来自祖先的消息');  
    
  const Childdata = ref('')
  function handleChildEvent(message: string) {  
    Childdata.value = message
  }  
  </script>
<template>  
    <div>  
        <div style="color: skyblue;">父组件</div>
      <Son v-bind="$attrs" v-on:child-event="$emit('child-event', $event)" />  
    </div>  
  </template>  
    
  <script setup lang="ts">  
  import Son from './son.vue'; 
  </script>
<template>  
    <div>  
      <h3 style="color: pink;">我是子孙组件</h3>  
      <button @click="sendMessageToAncestor">发送消息给祖先组件</button>  
    </div>  
  </template>  
    
  <script setup lang="ts">  
  import { defineEmits } from 'vue';  
    
  const emit = defineEmits<{  
    (e: 'child-event', message: string): void;  
  }>();  
    
  function sendMessageToAncestor() {  
    emit('child-event', '来自子孙组件的消息');  
  }  
  </script>

七、Provide与inject(Provide组件)

在Vue 3中,provide 和 inject 是用于实现跨组件层级通信的API。provide 选项允许你指定你想要提供给后代组件的数据/方法,而 inject 选项则用于接收这些数据/方法。

<template>  
<div>  
    <div style="color:skyblue;">我是父组件</div>  
    <ChildComponent/>  
</div>  
</template>  
<script setup>  
import { provide, ref } from 'vue';  
import ChildComponent from './ChildComponent.vue';  
const theme = ref('dark');  
provide('theme', theme);  
</script>
<template>  
<div>  
    <h2 style="color:orange;">我是子组件</h2>  
    <GrandchildComponent/>  
</div>  
</template>  

<script setup>  
import GrandchildComponent from './GrandchildComponent.vue';  
</script>
<template>  
<div>  
    <h3 style="color:pink;">我是孙子组件,当前主题是:{{ theme }}</h3>  
</div>  
</template>  

<script setup>  
import { inject } from 'vue';  

const theme = inject('theme', 'default'); // 第二个参数是默认值,可选  
</script>

Vuex和pinia区别 状态管理Vuex和pinia区别

八、Vuex状态管理(vue2使用)

1726121587517.png

Vuex 的核心概念包括:

1、 State:单一状态树,用一个对象就包含了全部的应用层级状态。

2、Getters:允许组件从 Store 中获取数据,就像计算属性一样。

3、Mutations:更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。 Mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type) 和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更新的地方,并且它会接受 state 作为第一个参数。

4、Actions:Action 类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态;Action 可以包含任意异步操作。

5、Modules:由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决这个问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。

九、pinia状态管理(vue3使用)

image.png

Pinia是Vue的一个状态管理库,特别是为Vue 3设计,提供了一种简单、直观且可扩展的方式来组织和访问应用程序的状态。以下是关于Pinia状态管理的详细介绍:

一、Pinia的特点

1、轻量级与易用性:Pinia相比Vuex更加轻量级,减少了不必要的复杂性和概念。它的API设计更加简单直观,特别是利用了Vue 3的新特性,如Composition API,使得开发者能够更容易地上手和使用。

2、模块化状态管理:Pinia支持将状态划分为不同的模块,提高了代码的可维护性和可扩展性。每个模块可以包含自己的state、getters和actions,使得状态管理更加清晰和有条理。 T

3、ypeScript支持:Pinia提供了对TypeScript的良好支持,包括类型推断和自动补全功能,使得在TypeScript项目中使用Pinia更加方便。这有助于在开发过程中捕获错误和进行静态类型检查。

4、状态订阅与变更:Pinia允许开发者订阅状态的变化,并在状态发生变化时触发相应的回调函数。同时,它也支持使用patch方法来批量更新状态,以及通过reset方法将状态重置为初始值。

5、与Vue.js生态系统的集成:Pinia与Vue.js的其他工具和库配合良好,可以轻松地与Vuex、Vue Router等一起使用。

此外,它还支持Vue Devtools,方便开发者进行调试。 插件系统:Pinia允许通过插件来扩展其功能,例如支持服务端渲染或添加额外的中间件。

二、Pinia的核心概念Store:

1、Store:Pinia中的Store是一个保存状态和业务逻辑的实体,它不与组件树绑定,可以在整个应用中访问。

Store包含三个核心概念:state(状态)、getters(获取器)、actions(动作)。

state:状态数据,通常是响应式的,可以在组件中被读取和修改。

getters:计算属性,用于派生状态,它们是响应式的,并且可以被缓存。

actions:可以包含任意的异步操作或同步操作,用于修改状态或执行复杂的业务逻辑。