目录

  • 前言
  • props 父传子
      • 原理说明
      • 使用场景
      • 代码示例
        • 父组件 PropsTest.vue
        • 子组件 Child.vue
  • 自定义事件 $emit 子传父
      • 原理说明
      • 使用场景
      • 代码示例
        • 父组件 EventTest.vue
        • 子组件 Event2.vue
  • Event Bus 兄弟/跨层通信
      • 原理说明
      • 使用场景
      • 代码示例
        • 事件总线 bus/index.ts
        • 兄弟组件通信示例
          • Child2.vue(发送事件的组件,发布者)
          • Child1.vue(接收事件的组件,订阅者)
          • EventBusTest.vue(测试入口,父组件)
  • v-model 父子双向绑定
      • 原理说明
      • 使用场景
      • 代码示例
        • 父组件 ModelTest.vue
        • 子组件 Child.vue
  • \$attrs / $listeners 属性和事件透传
      • 原理说明
      • 使用场景
      • 代码示例
        • 父组件 AttrsListenersTest.vue
        • 子组件 HintButton.vue
  • ref / parent / children 获取组件实例
      • 原理说明
      • 使用场景
      • 代码示例
        • 父组件 RefChildrenParentTest.vue
        • 子组件 Son.vue
        • 子组件 Daughter.vue
  • provide / inject 跨级通信
      • 原理说明
      • 使用场景
      • 代码示例
        • 祖先组件 ProvideInjectTest.vue
        • 子组件 Child.vue
        • 孙子组件 GrandChild.vue
  • Pinia 全局状态管理
      • 原理说明
      • 使用场景
      • 代码示例
        • 1. 安装 Pinia
        • 2. 创建并注册 Pinia
        • 3. 定义 Store(全局计数器,js 版)
        • 4. 父组件下两个子组件共享 store
          • 父组件 index.vue
          • 子组件 Child.vue(子组件A)
          • 子组件 Child1.vue(子组件B)
  • Slot 插槽内容分发
      • 原理说明
      • 使用场景
      • 代码示例
        • 1. SlotTest.vue(父组件)
        • 2. Test.vue(子组件,包含默认插槽和具名插槽)
        • 3. Test1.vue(子组件,仅包含默认插槽)

前言

组件通信是 Vue 开发中的核心知识点,也是初学者理解组件化思想的关键环节。在 Vue 应用中,组件并非孤立存在,它们之间需要通过数据传递、事件通知等方式协同工作,实现复杂的业务逻辑。

我系统整理了 Vue 中常用的组件通信方式,包括 props 父传子自定义事件子传父Event Bus 兄弟通信v-model 双向绑定$attrs 透传ref 获取组件实例provide/inject 跨级通信Pinia 全局状态管理Slot 插槽内容分发 共 9 种方案。

props 父传子

原理说明

props 是 Vue 组件间最基础、最常用的通信方式,核心遵循单向数据流原则。具体来说,父组件可以通过在子组件标签上定义属性(props)的方式传递数据;子组件则需要通过 defineProps 函数明确声明接收这些数据。这里的单向数据流非常关键:子组件只能读取 props 中的数据,绝对不能直接修改,如果子组件需要更新数据,必须通过通知父组件,由父组件修改数据源后重新传递。这种机制保证了数据流向的清晰可追踪,避免了组件间数据混乱。

使用场景

适用于所有父组件向子组件传递数据的场景,例如:

  • 父组件将用户基本信息(姓名、年龄)传递给个人资料子组件展示;
  • 父组件将配置参数(如是否显示边框、主题颜色)传递给子组件控制样式;
  • 父组件将列表数据传递给子组件进行渲染展示。

代码示例

父组件 PropsTest.vue
<template><div class="container"><h2>父组件</h2><!-- 通过 fatherName(静态字符串)和 sonAge(响应式数据)两个 props 向子组件传递数据 --><Son fatherName="Tom" :sonAge="age" /></div>
</template><script setup>
// 导入子组件
import Son from "./Child.vue";
// 导入 Vue 的 ref 函数创建响应式数据
import { ref } from "vue";
// 定义响应式变量 age,初始值为 12
const age = ref(12);
</script><style scoped>
.container {padding: 16px;background: #f5f5f5;
}
</style>

父组件通过两种方式传递 props:

  • fatherName="Tom":静态字符串传递,无需 v-bind: 是缩写);
  • :sonAge="age":响应式数据传递,必须用 v-bind 绑定,父组件数据更新时子组件会自动同步。
子组件 Child.vue
<template><div class="child"><h3>子组件</h3><!-- 直接使用接收的 props 数据渲染 --><p>父亲名字: {{ fatherName }}</p><p>儿子年龄: {{ sonAge }}</p><!-- 尝试修改 props 的按钮 --><button @click="tryModify">尝试修改 props</button></div>
</template><script setup>
// 通过 defineProps 声明接收的 props 名称,参数是数组形式的 props 列表
const props = defineProps(['fatherName', 'sonAge']);
// 解构 props 中的数据,方便在模板和脚本中使用
const { fatherName, sonAge } = props;// 尝试修改 props 的函数(实际无效)
const tryModify = () => {// 弹出提示:props 是只读的,直接修改会报错alert('props 是只读的,不能直接修改');
};
</script><style scoped>
.child {padding: 12px;background: #e0f7fa;
}
</style>

子组件核心逻辑说明:

  • 通过 defineProps(['fatherName', 'sonAge']) 明确声明需要接收的 props,确保数据来源清晰;
  • 解构 props 后可直接在模板中用 {{ fatherName }} 渲染,或在脚本中使用;
  • tryModify 函数验证了 props 的只读性,直接修改(如 sonAge = 13)会触发 Vue 警告。

在这里插入图片描述

自定义事件 $emit 子传父

原理说明

当子组件需要向父组件传递数据或通知父组件执行操作时,可通过自定义事件实现。核心流程是:

  1. 子组件通过 defineEmits 提前声明要触发的自定义事件名称,明确事件类型;
  2. 子组件在特定时机(如按钮点击、数据变化)通过 emit 方法触发声明的事件,并可携带数据;
  3. 父组件在使用子组件时,通过 @事件名 监听子组件触发的事件,并在事件处理函数中接收子组件传递的数据。

这种方式实现了子组件到父组件的反向通信,是 Vue 中“子传父”的标准方案。

使用场景

适用于子组件有用户交互或内部状态变化需要通知父组件的场景,例如:

  • 子组件的表单提交按钮被点击,需要将表单数据传递给父组件保存;
  • 子组件的删除按钮被点击,需要通知父组件删除对应数据;
  • 子组件的下拉菜单选择项变化,需要将选中值传递给父组件。

代码示例

父组件 EventTest.vue
<template><div><!-- 监听子组件 UserForm 的自定义事件 submitUser,绑定处理函数 handleUserSubmit --><UserForm @submitUser="handleUserSubmit" /></div>
</template><script setup>
// 导入子组件
import UserForm from './Event2.vue';// 定义事件处理函数,参数 user 接收子组件传递的数据
const handleUserSubmit = (user) => {console.log('收到子组件提交的用户:', user); // 控制台输出子组件传递的用户信息
};
</script>

父组件核心逻辑:通过 @submitUser="handleUserSubmit" 监听子组件的 submitUser 事件,当子组件触发该事件时,handleUserSubmit 函数会被调用,参数即为子组件传递的数据。

子组件 Event2.vue
<template><div class="form"><!-- 点击按钮触发 submit 函数 --><button @click="submit">提交用户信息</button></div>
</template><script setup>
// 通过 defineEmits 声明要触发的自定义事件,参数是数组形式的事件列表
const emit = defineEmits(['submitUser']);// 按钮点击的处理函数
const submit = () => {// 子组件内部准备需要传递给父组件的数据const user = { name: 'Alice', age: 20 };// 触发自定义事件 submitUser,并传递 user 数据emit('submitUser', user);
};
</script><style scoped>
.form {padding: 12px;background: #fff3e0;
}
</style>

子组件核心逻辑:

  • defineEmits(['submitUser']) 声明要触发的事件 submitUser,确保事件来源可追溯;
  • submit 函数在按钮点击时执行,内部创建用户数据 user
  • 通过 emit('submitUser', user) 触发事件并传递数据,父组件的监听函数会接收该数据。
    在这里插入图片描述
    在这里插入图片描述

Event Bus 兄弟/跨层通信

原理说明

Event Bus(事件总线)是一种基于发布-订阅模式的跨组件通信方案,核心是创建一个全局的事件中心(通常是一个能触发和监听事件的对象)。具体流程:

  1. 所有组件都可以访问这个全局事件中心;
  2. 需要发送数据的组件(发布者)通过事件中心的 emit 方法发布事件,并携带数据;
  3. 需要接收数据的组件(订阅者)通过事件中心的 on 方法订阅对应事件,并在事件回调中处理数据;
  4. 组件销毁时需通过 off 方法取消订阅,避免内存泄漏(示例中简化未展示)。

在 Vue3 中,官方推荐使用 mitt 库实现事件总线(Vue2 中常用 Vue.prototype.$bus = new Vue(),但 Vue3 不再支持)。

使用场景

适用于无直接父子关系的组件间通信,例如:

  • 兄弟组件之间的通信(如页面左侧导航和右侧内容区的交互);
  • 跨多层级的组件通信(如孙子组件和祖父组件的通信,且中间层级无需关心数据);
  • 非嵌套关系的任意组件间数据传递。

代码示例

事件总线 bus/index.ts
// 导入 mitt 库(需先通过 npm install mitt 安装)
import mitt from 'mitt';
// 创建 mitt 实例作为全局事件中心
const bus = mitt();
// 导出事件中心,供所有组件使用
export default bus;

这是事件总线的核心文件,创建了一个全局可访问的事件中心 bus,所有组件通过导入该 bus 实现通信。

兄弟组件通信示例
Child2.vue(发送事件的组件,发布者)
<script setup>
// 导入全局事件中心 bus
import bus from '../../bus';// 定义发送数据的函数
const sendStudent = () => {// 通过 bus.emit 发布事件 updateStudent,并携带学生数据bus.emit('updateStudent', { name: 'Tom', grade: 3 });
};
</script>
<template><div class="child"><!-- 点击按钮触发 sendStudent 函数,发布事件 --><button @click="sendStudent">发送学生信息</button></div>
</template>
<style scoped>
.child { padding: 12px; background: #e3f2fd; }
</style>

Child2 是数据的发送方:通过 bus.emit('事件名', 数据) 发布事件,其他组件可订阅该事件接收数据。

Child1.vue(接收事件的组件,订阅者)
<script setup>
// 导入全局事件中心 bus
import bus from '../../bus';
// 导入 Vue 的 onMounted 生命周期钩子,确保组件挂载后再订阅事件
import { onMounted } from 'vue';// 组件挂载后执行
onMounted(() => {// 通过 bus.on 订阅 updateStudent 事件,回调函数接收发布者传递的数据bus.on('updateStudent', (student) => {console.log('收到学生信息:', student); // 控制台输出接收的数据});
});
</script>
<template><div class="child"><p>等待接收学生信息...</p></div>
</template>
<style scoped>
.child { padding: 12px; background: #fffde7; }
</style>

Child1 是数据的接收方:在 onMounted 生命周期中通过 bus.on('事件名', 回调函数) 订阅事件,当事件被发布时,回调函数会被触发并接收数据。

EventBusTest.vue(测试入口,父组件)
<template><div class="container"><!-- 引入两个子组件,形成兄弟关系 --><Child1 /><Child2 /></div>
</template>
<script setup>
// 导入两个子组件
import Child1 from './Child1.vue';
import Child2 from './Child2.vue';
</script>
<style scoped>
.container { display: flex; gap: 16px; } /* 横向排列两个子组件 */
</style>

该组件作为父组件,同时引入 Child1 和 Child2,使两者成为兄弟组件。点击 Child2 的按钮,Child1 会通过事件总线收到数据,实现兄弟组件通信。
在这里插入图片描述
在这里插入图片描述

v-model 父子双向绑定

原理说明

v-model 是 Vue 提供的父子组件双向数据绑定语法糖,本质是对“父传子 props + 子传父事件”的简化封装。核心流程:

  1. 父组件使用 v-model="数据" 绑定数据,等价于 :modelValue="数据" @update:modelValue="数据 = $event"
  2. 子组件通过 defineProps 接收 modelValue(默认名称,可自定义),作为展示的数据源;
  3. 子组件数据变化时,通过 emit('update:modelValue', 新数据) 触发事件,父组件会自动更新绑定的数据;
  4. 最终实现父子组件数据的实时同步,一方变化另一方自动更新。

使用场景

适用于需要父子组件数据实时同步的场景,例如:

  • 自定义输入框组件(输入内容实时同步到父组件);
  • 开关组件(开关状态在父子组件同步);
  • 滑块组件(滑动值实时反馈给父组件)。

代码示例

父组件 ModelTest.vue
<template><div><!-- v-model 绑定 personName,实现父子双向绑定 --><PersonInput v-model="personName" /></div>
</template>
<script setup>
// 导入子组件
import PersonInput from './Child.vue';
// 导入 ref 创建响应式数据
import { ref } from 'vue';
// 定义响应式变量 personName,初始值为 'Tom'
const personName = ref('Tom');
</script>

父组件核心:v-model="personName" 等价于:

<PersonInput :modelValue="personName" @update:modelValue="personName = $event" />

无需手动写 props 和事件监听,简化了双向绑定的写法。

子组件 Child.vue
<template><div class="input-box"><!-- 输入框的值绑定到 modelValue(父组件通过 v-model 传递) --><input :value="modelValue" @input="onInput" /><!-- 按钮点击触发 updateName 函数 --><button @click="updateName">修改名字</button></div>
</template>
<script setup>
// 接收父组件通过 v-model 传递的 modelValue
const props = defineProps(['modelValue']);
// 声明要触发的 update:modelValue 事件
const emit = defineEmits(['update:modelValue']);// 输入框输入事件处理函数
const onInput = (e) => {// 触发 update:modelValue 事件,传递输入框的最新值(e.target.value)emit('update:modelValue', e.target.value);
};
// 按钮点击修改名字的函数
const updateName = () => {// 主动触发事件,传递新值 'Jerry'emit('update:modelValue', 'Jerry');
};
</script>
<style scoped>
.input-box { padding: 12px; background: #f1f8e9; }
</style>

子组件核心逻辑:

  • 通过 defineProps(['modelValue']) 接收父组件传递的初始值;
  • 输入框 :value="modelValue" 绑定展示值,输入时触发 onInput 函数,通过 emit 传递新值更新父组件;
  • updateName 函数主动触发事件修改值,体现双向绑定的灵活性:无论是用户输入还是代码触发,都能同步到父组件。
    在这里插入图片描述

$attrs / $listeners 属性和事件透传

原理说明

在组件嵌套层级较深时,父组件传递的属性和事件可能需要逐层传递到底层组件,$attrs 和属性透传机制可简化这一过程:

  • $attrs 是一个对象,包含父组件传递给子组件、但未被子组件通过 defineProps 声明接收的所有属性和事件(class 和 style 除外,它们会自动合并);
  • 子组件可通过 v-bind="$attrs"$attrs 中的所有属性和事件透传到内部的子组件(通常是原生 HTML 元素或基础组件);
  • 这种方式避免了中间组件手动声明大量 props 和事件,减少冗余代码。

Vue3 中 $listeners 已被合并到 $attrs 中,无需单独处理。

使用场景

适用于组件封装层级较深,需要透传属性和事件的场景,例如:

  • UI 组件库封装(如封装 Button 组件时,透传原生 button 的所有属性和事件);
  • 多层嵌套组件中,上层组件传递的属性需要直接作用于最底层组件。

代码示例

父组件 AttrsListenersTest.vue
<template><div><!-- 向 ActionButton 传递 type、label、@action 等属性和事件 --><ActionButton type="primary" label="保存" @action="handleAction" /></div>
</template>
<script setup>
// 导入子组件
import ActionButton from './HintButton.vue';
// 定义 action 事件的处理函数
const handleAction = () => {alert('执行操作!'); // 点击按钮时触发
};
</script>

父组件向 ActionButton 传递了:

  • 属性 type="primary"(原生 button 的 type 属性);
  • 属性 label="保存"(自定义属性,用于按钮文本);
  • 事件 @action="handleAction"(自定义事件,按钮点击时触发)。
子组件 HintButton.vue
<template><!-- 通过 v-bind="$attrs" 将 $attrs 中的属性和事件透传给 button 元素 --><button v-bind="$attrs">{{ label }}</button>
</template>
<script setup>
// 导入 useAttrs 函数获取 $attrs 对象
import { useAttrs } from 'vue';
// 通过 defineProps 接收 label 属性(需要单独处理的属性)
const props = defineProps(['label']);
// 获取 $attrs 对象(包含未被 props 接收的属性和事件)
const $attrs = useAttrs();
</script>

子组件核心逻辑:

  • 通过 defineProps(['label']) 声明接收 label 属性,用于在模板中展示按钮文本 {{ label }}
  • 未被 defineProps 接收的 type="primary"@action 事件会自动进入 $attrs
  • 通过 v-bind="$attrs"$attrs 中的属性和事件透传给原生 button 元素,最终:
    • button 会拥有 type="primary" 属性;
    • button 的点击事件会触发父组件的 handleAction 函数(因为 @action 事件被透传)。

这种方式下,中间组件(HintButton)无需声明 type@action,直接透传到底层 button 元素,减少代码冗余。
在这里插入图片描述

ref / parent / children 获取组件实例

原理说明

在某些场景下,父组件需要直接访问子组件的属性或调用子组件的方法,或子组件需要访问父组件的实例,可通过以下方式实现:

  • ref 获取子组件实例:父组件在子组件标签上添加 ref="变量名",通过 变量名.value 获取子组件实例;子组件需通过 defineExpose 显式暴露需要被访问的属性和方法(默认情况下,setup 中的内容是私有的)。
  • **parent获取父组件实例∗∗:子组件中通过‘parent 获取父组件实例**:子组件中通过 `parent获取父组件实例:子组件中通过parent` 可直接获取父组件的实例,进而访问父组件的属性和方法(需注意层级关系,避免过度依赖导致耦合)。

这种方式直接操作组件实例,灵活性高,但会增加组件间的耦合度,需谨慎使用。

使用场景

适用于父组件需要直接控制子组件行为的场景,例如:

  • 父组件需要调用子组件的初始化方法或重置方法;
  • 子组件需要获取父组件的样式或状态(如示例中获取父组件的 class)。

代码示例

父组件 RefChildrenParentTest.vue
<template><div class="container"><p>学生当前分数: {{ studentRef?.score }}</p> <!-- 通过 ref 访问子组件的 score 属性 --><!-- 通过 ref="studentRef" 绑定 Son 组件实例 --><Son ref="studentRef" /><!-- 点击按钮调用子组件的 study 方法 --><button @click="callStudy">让学生学习</button><!-- 通过 ref="daughterRef" 绑定 Daughter 组件实例 --><Daughter ref="daughterRef" /></div>
</template>
<script setup>
// 导入子组件
import Son from './Son.vue';
import Daughter from './Daughter.vue';
// 导入 ref 创建用于绑定组件实例的变量
import { ref } from 'vue';
// 创建 ref 变量存储 Son 组件实例
const studentRef = ref();
// 创建 ref 变量存储 Daughter 组件实例(示例中未使用,仅展示绑定方式)
const daughterRef = ref();
// 定义调用子组件方法的函数
const callStudy = () => {// 通过 studentRef.value 获取子组件实例,调用其暴露的 study 方法studentRef.value.study();
};
</script><style scoped>
p{ width: 200px;height: auto;background: #f0f0f0;
}
.container{width: 100vw;height:400px;padding: 10px;background: #b9dcea; /* 父组件的 class 样式 */
}
</style>

父组件核心逻辑:

  • 通过 ref="studentRef" 绑定 Son 组件,studentRef.value 即为 Son 组件的实例;
  • studentRef?.score 访问子组件暴露的 score 属性(?. 是可选链,避免未挂载时报错);
  • callStudy 函数通过 studentRef.value.study() 调用子组件暴露的 study 方法。
子组件 Son.vue
<template><div class="student"><h3>学生分数: {{ score }}</h3> <!-- 展示 score 属性 --></div>
</template><script setup>
// 导入 ref 创建响应式分数
import { ref } from 'vue';
// 定义分数属性(初始值 80)
const score = ref(80);
// 定义学习方法(调用时分数增加 5)
const study = () => {score.value += 5;console.log('学生正在学习,分数提升!');
};
// 通过 defineExpose 显式暴露 score 属性和 study 方法,供父组件访问
defineExpose({ score, study });
</script><style scoped>
.student {width: 200px;height: auto;background: #e0f7fa;
}
</style>

Son 组件核心:通过 defineExpose({ score, study })scorestudy 暴露给父组件,父组件才能通过 ref 访问,否则无法访问 setup 中的私有变量和方法。

子组件 Daughter.vue
<template><div><h2>父组件class: {{ parentClass }}</h2> <!-- 展示父组件的 class --><!-- 点击按钮调用 getParentClass 函数,传递 $parent 获取父组件实例 --><button @click="getParentClass($parent)">获取父组件class</button></div>
</template><script setup>
// 导入 ref 存储父组件的 class
import { ref } from 'vue';
// 定义变量存储父组件的 class
const parentClass = ref('未知');
// 定义获取父组件 class 的函数
const getParentClass = ($parent) => {// 通过 $parent 获取父组件实例,$el 是组件的根 DOM 元素,className 是其 class 属性parentClass.value = $parent.$el.className;
};
</script><style scoped>
button {margin:10px 0 10px 0;
}
</style>

Daughter 组件核心:通过 $parent 获取父组件实例,$parent.$el 访问父组件的根 DOM 元素,进而获取其 class 属性,展示了子组件访问父组件实例的方式。
在这里插入图片描述

provide / inject 跨级通信

原理说明

provide/inject 是 Vue 提供的跨多层级组件通信方案,专门解决父子组件嵌套层级较深时,数据逐层传递(props 钻取)的问题:

  • 祖先组件通过 provide 方法提供数据(可以是响应式数据或普通值),指定一个注入名和对应的值;
  • 任意后代组件(无论层级多深)通过 inject 方法注入数据,使用注入名获取祖先组件提供的值;
  • 若提供的是响应式数据(如 refreactive 对象),后代组件修改数据会影响所有使用该数据的组件,实现跨层级数据同步。

使用场景

适用于跨多层级组件共享数据的场景,例如:

  • 全局配置(如主题颜色、语言设置)在所有组件中共享;
  • 权限信息在多层级组件中使用;
  • 框架级别的数据传递(如组件库中的上下文配置)。

代码示例

祖先组件 ProvideInjectTest.vue
<template><div class="container"><h2>Provide/Inject 跨级通信示例</h2><p>祖先组件 config: {{ config }}</p> <!-- 展示提供的 config 数据 --><Child /> <!-- 引入子组件,形成层级:祖先 -> 子 -> 孙子 --></div>
</template><script setup>
// 导入 ref 创建响应式数据,导入 provide 方法提供数据
import { ref, provide } from 'vue';
// 导入子组件
import Child from './Child.vue';
// 定义响应式 config 数据,初始主题为 light
const config = ref({ theme: 'light' });
// 通过 provide 提供数据,注入名为 'appConfig',值为 config
provide('appConfig', config);
</script><style scoped>
.container {width: 400px;min-height: 180px;background: #98d9ff; /* 初始主题颜色(light 模式) */padding: 20px;
}
</style>

祖先组件核心:通过 provide('appConfig', config) 提供数据,'appConfig' 是注入名(后代组件需用相同名称注入),config 是响应式数据,后代组件可获取并修改。

子组件 Child.vue
<template><div class="child"><div>儿子</div><GrandChild /> <!-- 引入孙子组件,形成更深层级 --></div>
</template><script setup>
// 导入孙子组件
import GrandChild from './GrandChild.vue';
</script><style scoped>
.child {width: 350px;min-height: 120px;background: #67bced;padding: 16px;
}
</style>

该组件是中间层级,仅作为嵌套容器,无需处理 provide/inject 数据,体现了 provide/inject 跳过中间层级的优势。

孙子组件 GrandChild.vue
<template><div class="grandchild"><div>孙子</div><!-- 点击按钮切换主题 --><button @click="updateTheme">切换主题</button></div>
</template><script setup>
// 导入 inject 方法注入数据
import { inject } from 'vue';
// 通过 inject 注入祖先组件提供的 appConfig 数据,设置默认值防止未提供时为 undefined
const config = inject('appConfig', { value: { theme: 'light' } });
// 定义切换主题的函数
const updateTheme = () => {// 检查 config 是响应式数据(ref 对象),通过 .value 访问if (config && config.value) {// 切换主题(light <-> dark)config.value.theme = config.value.theme === 'light' ? 'dark' : 'light';// 根据主题修改祖先组件的背景色document.querySelector('.container').style.backgroundColor = config.value.theme === 'light' ? '#98d9ff' : '#135074';}
};
</script>
<style scoped>
.grandchild {margin:10px;padding: 8px;background: #98d9ff;
}
</style>

孙子组件核心:

  • 通过 inject('appConfig', 默认值) 获取祖先组件提供的 config 数据,注入名必须与 provide 时一致;
  • config 是响应式 ref 对象,通过 config.value 访问和修改其属性;
  • updateTheme 函数修改 config.value.theme,由于是响应式数据,所有使用该数据的组件(包括祖先组件)都会感知变化,实现跨层级数据同步。
    在这里插入图片描述

Pinia 全局状态管理

原理说明

Pinia 是 Vue3 官方推荐的全局状态管理库,替代了 Vue2 中的 Vuex,核心优势是支持响应式、模块化和 TypeScript 类型推导。其工作原理:

  • 通过 defineStore 定义一个 store(仓库),包含 state(存储数据)、actions(修改数据的方法)等;
  • store 中的 state 是响应式的,任意组件获取 state 后,数据变化会触发组件重新渲染;
  • 组件通过导入 store 并调用其 stateactions,实现跨组件数据共享和修改;
  • 整个应用的状态集中管理,避免了组件间通信的繁琐,适合全局数据共享。

使用场景

适用于全局数据需要在多个组件间共享和同步的场景,例如:

  • 用户登录状态(用户名、权限)在所有组件中使用;
  • 购物车数据在商品列表、购物车页面、结算页面同步;
  • 全局计数器、通知消息等需要跨组件访问的数据。

代码示例

1. 安装 Pinia
npm install pinia

首先通过 npm 安装 Pinia 库,确保项目中可使用其 API。

2. 创建并注册 Pinia
// src/store/index.js
import { createPinia } from 'pinia';
// 创建 Pinia 实例
const pinia = createPinia();
// 导出实例供 app 使用
export default pinia;// main.ts
import { createApp } from 'vue';
import App from './App.vue';
// 导入 Pinia 实例
import pinia from './store';
// 创建 Vue 应用
const app = createApp(App);
// 应用 Pinia 插件
app.use(pinia);
// 挂载应用
app.mount('#app');

创建 Pinia 实例并通过 app.use(pinia) 注册到 Vue 应用,使整个应用都能使用 Pinia 的功能。

3. 定义 Store(全局计数器,js 版)
// src/store/modules/info.js
import { defineStore } from 'pinia';// 通过 defineStore 定义 store,第一个参数是唯一 id(需全局唯一),第二个参数是配置对象
export const useInfoStore = defineStore('info', {// state 是函数,返回初始状态对象state: () => ({count: 0 // 全局计数器,初始值 0}),// actions 是对象,包含修改 state 的方法(可异步)actions: {// 增加计数器的方法increment() {this.count++; // this 指向 store 实例,直接修改 state},// 减少计数器的方法decrement() {this.count--;}}
});

定义了一个名为 info 的 store,包含 count 状态和修改它的 incrementdecrement 方法,所有组件都可访问该 store。

4. 父组件下两个子组件共享 store
父组件 index.vue
<template><div class="container"><h2>Pinia 父子组件共享状态示例</h2><!-- 引入两个子组件,它们将共享同一个 store --><Child /><Child1 /></div>
</template>
<script setup>
// 导入子组件
import Child from './Child.vue';
import Child1 from './Child1.vue';
</script>

父组件仅作为容器,引入两个子组件,两个子组件将通过 Pinia 共享全局状态。

子组件 Child.vue(子组件A)
<template><div class="child"><h3>子组件A</h3><p>全局计数: {{ infoStore.count }}</p> <!-- 展示 store 中的 count --><!-- 点击按钮调用 store 的 increment 方法 --><button @click="infoStore.increment">增加</button></div>
</template>
<script setup>
// 导入定义的 store
import { useInfoStore } from '@/store/modules/info.js';
// 获取 store 实例
const infoStore = useInfoStore();
</script>

子组件A核心:通过 useInfoStore() 获取 store 实例,直接访问 infoStore.count 展示数据,点击按钮调用 increment 方法增加计数。

子组件 Child1.vue(子组件B)
<template><div class="child"><h3>子组件B</h3><p>全局计数: {{ infoStore.count }}</p> <!-- 展示同一个 store 的 count --><!-- 点击按钮调用 store 的 decrement 方法 --><button @click="infoStore.decrement">减少</button></div>
</template>
<script setup>
// 导入定义的 store
import { useInfoStore } from '@/store/modules/info.js';
// 获取 store 实例(与子组件A的实例相同)
const infoStore = useInfoStore();
</script>

子组件B核心:同样通过 useInfoStore() 获取同一个 store 实例,展示的 count 与子组件A完全同步,点击按钮调用 decrement 方法减少计数。

这样,父组件下的两个子组件都能共享和操作同一个全局状态 count,实现响应式同步。无论哪个组件修改 count,另一个组件都会实时更新,体现了 Pinia 全局状态管理的优势。
在这里插入图片描述

Slot 插槽内容分发

原理说明

Slot(插槽)是 Vue 提供的组件内容分发机制,允许父组件向子组件的指定位置插入自定义内容,使子组件更灵活可定制。核心概念:

  • 默认插槽:子组件中用 <slot></slot> 定义一个默认插入位置,父组件在子组件标签内的内容会默认插入到该位置;
  • 具名插槽:子组件中用 <slot name="插槽名"></slot> 定义多个有名称的插槽,父组件通过 <template #插槽名> 指定内容插入到对应插槽;
  • 插槽内容由父组件提供,子组件负责定义插槽位置和样式,实现内容与结构的分离。

使用场景

适用于组件需要支持自定义内容的场景,例如:

  • 组件库开发(如卡片组件的头部、内容、底部可自定义);
  • 布局组件(如侧边栏、主内容区的内容由父组件指定);
  • 表单组件(如输入框前缀、后缀内容自定义)。

代码示例

1. SlotTest.vue(父组件)
<template><div class="slot-container"><h2>Slot 插槽示例</h2><!-- 使用 Test 组件,提供插槽内容 --><Test><!-- 默认插槽内容:通过 <template #default> 指定插入到 Test 组件的默认插槽 --><template #default><div class="slot-block">默认插槽内容</div></template><!-- 具名插槽内容:通过 <template #named> 指定插入到 Test 组件的 named 插槽 --><template #named><div class="slot-block">具名插槽内容</div></template></Test><!-- 使用 Test1 组件,提供默认插槽内容 --><Test1><template #default><div class="slot-block">Test1 默认插槽内容</div></template></Test1></div>
</template>
<script setup>
// 导入子组件
import Test from './Test.vue';
import Test1 from './Test1.vue';
</script>
<style scoped>
.slot-container {width: 500px;min-height: 200px;background: #fbbaba;padding: 24px;
}
.slot-block {background: #fa676e;margin: 12px 0;padding: 16px;font-size: 16px;
}
</style>

父组件核心:通过 <template #插槽名> 为子组件的不同插槽提供内容,#default 可省略,直接在子组件标签内写内容即默认插槽。

2. Test.vue(子组件,包含默认插槽和具名插槽)
<template><div class="test-block"><h3>Test 组件</h3><!-- 默认插槽区域:父组件的 #default 内容插入到这里 --><div class="slot-area"><slot></slot></div><!-- 具名插槽 named 区域:父组件的 #named 内容插入到这里 --><div class="slot-area"><slot name="named"></slot></div></div>
</template>
<style scoped>
.test-block {background: #6ec2ff;padding: 18px;margin-bottom: 18px;
}
.slot-area {margin: 10px 0;padding: 10px;background: #c8fc8c; /* 插槽区域背景色,区分内容来源 */
}
</style>

Test 组件核心:定义了两个插槽:

  • <slot></slot>:默认插槽,接收父组件的 #default 内容;
  • <slot name="named"></slot>:具名插槽,接收父组件的 #named 内容;
  • 插槽所在的 .slot-area 定义了样式,使插入的内容有统一的展示风格。
3. Test1.vue(子组件,仅包含默认插槽)
<template><div class="test1-block"><h3>Test1 组件</h3><!-- 默认插槽区域:父组件的内容插入到这里 --><div class="slot-area"><slot></slot></div></div>
</template>
<style scoped>
.test1-block {background: #f3d527;padding: 18px;margin-bottom: 18px;
}
.slot-area {margin: 10px 0;padding: 10px;background: #fff897; /* 插槽区域背景色 */
}
</style>

Test1 组件仅定义了默认插槽,父组件在其标签内的内容会默认插入到 <slot></slot> 位置,展示了默认插槽的基本用法。

通过以上示例,父组件可根据需求为子组件提供自定义内容,子组件通过插槽定义内容位置和样式,实现了组件的高复用性和灵活性。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/92390.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/92390.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/92390.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【PTA数据结构 | C语言版】求最小生成树的Prim算法

本专栏持续输出数据结构题目集&#xff0c;欢迎订阅。 文章目录题目代码题目 请编写程序&#xff0c;实现在带权的无向图中求最小生成树的 Prim 算法。 注意&#xff1a;当多个待收录顶点到当前点集的距离等长时&#xff0c;按编号升序进行收录。 输入格式&#xff1a; 输入首…

【加解密与C】Rot系列(四)RotSpecial

RotSpecial 函数解析RotSpecial 是一个自定义函数&#xff0c;通常用于处理特定的旋转操作&#xff0c;尤其在图形变换或数据处理中。该函数可能涉及欧拉角、四元数或其他旋转表示方法&#xff0c;具体行为取决于实现上下文。以下是关于该函数的通用解释和可能的使用方法&#…

【机器学习深度学习】LLaMAFactory中的精度训练选择——bf16、fp16、fp32与pure_bf16深度解析

目录 前言 一、 为什么精度如此重要&#xff1f;—— 内存、速度与稳定性的三角博弈 二、 四大精度/模式详解&#xff1a; bf16, fp16, fp32, pure_bf16 三、 关键特性对比表 ▲四大计算类型核心对比表 ▲ 显存占用对比示例&#xff08;175B参数模型&#xff09; ▲LLa…

C# 基于halcon的视觉工作流-章21-点查找

C# 基于halcon的视觉工作流-章21-点查找 本章目标&#xff1a; 一、检测显著点&#xff1b; 二、Harris检测兴趣点&#xff1b; 三、Harris二项式检测兴趣点&#xff1b; 四、Sojka运算符检测角点&#xff1b; 五、Lepetit算子检测兴趣点&#xff1b;一、检测显著点 halcon算子…

(11)机器学习小白入门YOLOv:YOLOv8-cls epochs与数据量的关系

YOLOv8-cls epochs与数据量的关系 (1)机器学习小白入门YOLOv &#xff1a;从概念到实践 (2)机器学习小白入门 YOLOv&#xff1a;从模块优化到工程部署 (3)机器学习小白入门 YOLOv&#xff1a; 解锁图片分类新技能 (4)机器学习小白入门YOLOv &#xff1a;图片标注实操手册 (5)机…

Grafana | 如何将 11.x 升级快速到最新 12.x 版本?

[ 知识是人生的灯塔&#xff0c;只有不断学习&#xff0c;才能照亮前行的道路 ]&#x1f4e2; 大家好&#xff0c;我是 WeiyiGeek&#xff0c;一名深耕安全运维开发&#xff08;SecOpsDev&#xff09;领域的技术从业者&#xff0c;致力于探索DevOps与安全的融合&#xff08;Dev…

Dubbo + Spring Boot + Zookeeper 快速搭建分布式服务

Dubbo Spring Boot Zookeeper 快速搭建分布式服务 本文将详细介绍如何基于 Dubbo、Spring Boot 和 Zookeeper 快速搭建一个简单的分布式服务调用场景&#xff0c;包含服务提供者&#xff08;Provider&#xff09;、服务消费者&#xff08;Consumer&#xff09;及公共接口&…

五分钟掌握 TDengine 数据文件的工作原理

小 T 导读&#xff1a;今天我们来探讨一下——TDengine中的时序数据到底是如何存储的&#xff1f; 在上一期的文章《五分钟掌握 TDengine 时序数据的保留策略》中&#xff0c;我们知道了TDengine是如何按照时间段对数据进行分区来管理数据的。 接下来&#xff0c;我们和大家一起…

Python爬虫实战:研究http-parser库相关技术

一、研究背景与意义 在当今数字化时代,网络数据蕴含着巨大的价值。从商业决策、学术研究到社会治理,对海量网络信息的有效采集与分析至关重要。网络爬虫作为数据获取的核心工具,其性能与稳定性直接影响数据质量。然而,随着互联网技术的发展,网站反爬机制不断升级,传统爬…

Go语言实战案例-批量重命名文件

在《Go语言100个实战案例》中的 文件与IO操作篇 - 案例17&#xff1a;批量重命名文件 的完整内容&#xff0c;适合初学者实践如何使用 Go 操作文件系统并批量处理文件名。&#x1f3af; 案例目标实现一个小工具&#xff0c;能够批量重命名指定目录下的所有文件&#xff0c;例如…

基于单片机非接触红外测温系统

传送门 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品题目速选一览表 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品题目功能速览 概述 本设计实现了一种基于单片机的非接触式红外测温系统&#xff0c;适用于快速、安全测量物体表面温…

Python 入门手札:从 0 到会--第十天Python常用的第三方库Numpy,Pandas,Matplotlib

目录 一、Numpy 1.NumPy 是什么&#xff1f; 1.1安装numpy 1.2 导入numpy模块 2.NumPy 的核心&#xff1a;ndarray 2.1 什么是 ndarray&#xff1f; 2.2 ndarray 的创建方式 2.3 常见属性&#xff08;用于查看数组结构&#xff09; 2.4 ndarray 的切片与索引 2.5 ndarr…

mysql 性能优化之Explain讲解

EXPLAIN是 MySQL 中用于分析查询执行计划的重要工具&#xff0c;通过它可以查看查询如何使用索引、扫描数据的方式以及表连接顺序等信息&#xff0c;从而找出性能瓶颈。以下是关于EXPLAIN的详细介绍和实战指南&#xff1a;1. EXPLAIN 基本用法在SELECT、INSERT、UPDATE、DELETE…

Redis 连接:深度解析与最佳实践

Redis 连接:深度解析与最佳实践 引言 Redis 作为一款高性能的内存数据结构存储系统,在当今的互联网应用中扮演着越来越重要的角色。高效的 Redis 连接管理对于保证系统的稳定性和性能至关重要。本文将深入探讨 Redis 连接的原理、配置以及最佳实践,帮助读者更好地理解和应…

C语言---VSCODE的C语言环境搭建

文章目录资源下载配置环境验证资源下载 站内下载 配置环境 解压压缩包&#xff0c;复制以下文件的路径 打开主页搜索系统环境变量 点击环境变量 选择系统变量中的Path&#xff0c;点击编辑 在最后面添加路径。 添加完成记得关机重启。 验证 重启电脑之后打开在Power…

ojdbc对应jdk版本附下载地址(截止20250722)

可以从Oracle官网查看&#xff0c; JDBC and UCP Downloads page

Redis为什么被设计成是单线程的?

Redis单线程模型解析 当我们说Redis是单线程时,特指"其网络IO和键值对读写操作由单个线程完成"。实际上,Redis仅网络请求模块和数据操作模块采用单线程设计,而持久化存储、集群支持等其他模块都采用了多线程架构。 事实上,Redis从4.0版本就开始对部分命令实现了…

基础流程图

一、常用符号及定义二、 画图基础规则1、从上至下&#xff0c;从左至右流向顺序。2、开始符号只能有一个出口。3、进程符号不做校验逻辑。4、相同流程图&#xff0c;符号大小应为一致。5、引用流程&#xff0c;不重复绘制。6、路径符号尽量避免交叉重叠。7、同一路径&#xff0…

C# 结构体

目录 1.如何定义一个结构体&#xff08;struct 关键字&#xff09; 2.如何使用一个结构体 3.如何修改一个数据 4.如何让去访问一个学生的信息 5、结构体数组 练习 1.如何定义一个结构体&#xff08;struct 关键字&#xff09; C#中public 、private、protect的区别 结构…

在Python中操作Word

生成请假条1.准备一个文件“template.docx”&#xff0c;内容如下。2.安装docxtpl库。pip install docxtpl3.执行代码&#xff0c;替换字典内容。from docxtpl import DocxTemplate# 读取定义模板文件 tpl DocxTemplate(template.docx) # 创建子文档 sd tpl.new_subdoc() # 添…