news 2026/5/5 23:04:18

基础篇三 Nuxt4 组件进阶:插槽与事件传递

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基础篇三 Nuxt4 组件进阶:插槽与事件传递

文章目录

    • 一、插槽基础
    • 二、默认内容
    • 三、具名插槽
    • 四、作用域插槽
    • 五、高阶:表格组件
    • 六、事件传递:emit
    • 七、v-model 双向绑定
    • 八、多个 v-model
    • 九、透传属性
    • 十、组件引用
    • 总结

写过 Vue 的同学都知道,组件通信是个大话题。props 向下传,emit 向上发,听起来简单,但实际项目中各种复杂场景让人头大。今天深入聊聊组件间通信的进阶技巧——插槽和事件传递。

一、插槽基础

插槽让组件的内容可以由父组件决定:

<!-- components/MyCard.vue --> <template> <div class="card"> <slot /> </div> </template> <style scoped> .card { border: 1px solid #eee; border-radius: 8px; padding: 1rem; } </style>

使用时:

<MyCard> <h2>标题</h2> <p>这是卡片内容</p> </MyCard>

二、默认内容

插槽可以设置默认内容,父组件不传时显示:

<!-- components/MyButton.vue --> <template> <button class="btn"> <slot> 点击我 <!-- 默认内容 --> </slot> </button> </template>
<!-- 使用默认内容 --> <MyButton /> <!-- 覆盖默认内容 --> <MyButton>提交</MyButton>

三、具名插槽

一个组件可能有多个区域需要填充:

<!-- components/ArticleCard.vue --> <template> <article class="article-card"> <header> <slot name="header" /> </header> <main> <slot /> <!-- 默认插槽 --> </main> <footer> <slot name="footer" /> </footer> </article> </template>

使用:

<ArticleCard> <template #header> <h2>文章标题</h2> </template> <p>文章正文内容...</p> <template #footer> <span>2024-01-15</span> </template> </ArticleCard>

四、作用域插槽

父组件想用子组件的数据怎么办?作用域插槽:

<!-- components/UserList.vue --> <script setup lang="ts"> const users = [ { id: 1, name: 'Alice', age: 25 }, { id: 2, name: 'Bob', age: 30 }, { id: 3, name: 'Charlie', age: 35 } ] </script> <template> <ul> <li v-for="user in users" :key="user.id"> <slot :user="user" :index="user.id"> {{ user.name }} <!-- 默认渲染 --> </slot> </li> </ul> </template>

父组件自定义渲染:

<UserList> <template #default="{ user, index }"> <span>{{ index }}. {{ user.name }} ({{ user.age }}岁)</span> </template> </UserList>

五、高阶:表格组件

作用域插槽最经典的场景是表格组件:

<!-- components/DataTable.vue --> <script setup lang="ts"> interface Column { key: string title: string } const props = defineProps<{ columns: Column[] data: any[] }>() </script> <template> <table> <thead> <tr> <th v-for="col in columns" :key="col.key"> {{ col.title }} </th> </tr> </thead> <tbody> <tr v-for="(row, index) in data" :key="index"> <td v-for="col in columns" :key="col.key"> <slot :name="col.key" :row="row" :value="row[col.key]"> {{ row[col.key] }} </slot> </td> </tr> </tbody> </table> </template>

使用:

<script setup lang="ts"> const columns = [ { key: 'name', title: '姓名' }, { key: 'age', title: '年龄' }, { key: 'actions', title: '操作' } ] const users = [ { id: 1, name: 'Alice', age: 25 }, { id: 2, name: 'Bob', age: 30 } ] </script> <template> <DataTable :columns="columns" :data="users"> <!-- 自定义年龄列 --> <template #age="{ value }"> <span :class="{ 'text-red': value > 28 }"> {{ value }}岁 </span> </template> <!-- 自定义操作列 --> <template #actions="{ row }"> <button @click="edit(row)">编辑</button> <button @click="delete(row)">删除</button> </template> </DataTable> </template>

六、事件传递:emit

子组件向父组件传数据,用emit

<!-- components/Counter.vue --> <script setup lang="ts"> const count = ref(0) const emit = defineEmits<{ change: [value: number] reset: [] }>() const increment = () => { count.value++ emit('change', count.value) } const reset = () => { count.value = 0 emit('reset') } </script> <template> <div> <p>计数: {{ count }}</p> <button @click="increment">+1</button> <button @click="reset">重置</button> </div> </template>

父组件监听:

<script setup lang="ts"> const handleChange = (value: number) => { console.log('计数变化:', value) } const handleReset = () => { console.log('已重置') } </script> <template> <Counter @change="handleChange" @reset="handleReset" /> </template>

七、v-model 双向绑定

v-model本质是:value+@update:value的语法糖:

<!-- components/SearchInput.vue --> <script setup lang="ts"> const props = defineProps<{ modelValue: string }>() const emit = defineEmits<{ 'update:modelValue': [value: string] }>() const input = (e: Event) => { emit('update:modelValue', (e.target as HTMLInputElement).value) } </script> <template> <input :value="modelValue" @input="input" placeholder="搜索..." /> </template>

使用:

<script setup lang="ts"> const keyword = ref('') </script> <template> <SearchInput v-model="keyword" /> <p>搜索词: {{ keyword }}</p> </template>

Vue 3.4+ 可以用defineModel简化:

<!-- components/SearchInput.vue --> <script setup lang="ts"> const keyword = defineModel<string>() </script> <template> <input v-model="keyword" placeholder="搜索..." /> </template>

八、多个 v-model

一个组件可以有多个双向绑定:

<!-- components/DateRange.vue --> <script setup lang="ts"> const startDate = defineModel<Date>('startDate') const endDate = defineModel<Date>('endDate') </script> <template> <div class="date-range"> <input type="date" v-model="startDate" /> <span>至</span> <input type="date" v-model="endDate" /> </div> </template>

使用:

<script setup lang="ts"> const start = ref<Date>() const end = ref<Date>() </script> <template> <DateRange v-model:start-date="start" v-model:end-date="end" /> </template>

九、透传属性

有时候组件只是一个包装器,需要把所有属性传给内部元素:

<!-- components/MyButton.vue --> <script setup lang="ts"> // 不声明 props,属性会自动透传到根元素 </script> <template> <button class="my-button"> <slot /> </button> </template>

使用时,typedisabled等属性会自动传给<button>

<MyButton type="submit" disabled>提交</MyButton>

渲染结果:

<buttonclass="my-button"type="submit"disabled>提交</button>

禁用透传:

<script setup lang="ts"> defineOptions({ inheritAttrs: false }) const attrs = useAttrs() </script> <template> <div> <input v-bind="attrs" /> </div> </template>

十、组件引用

有时候需要调用子组件的方法:

<!-- components/Modal.vue --> <script setup lang="ts"> const visible = ref(false) const open = () => { visible.value = true } const close = () => { visible.value = false } // 暴露方法给父组件 defineExpose({ open, close }) </script> <template> <Teleport to="body"> <div v-if="visible" class="modal"> <slot /> <button @click="close">关闭</button> </div> </Teleport> </template>

父组件:

<script setup lang="ts"> const modalRef = ref<{ open: () => void; close: () => void }>() const showModal = () => { modalRef.value?.open() } </script> <template> <button @click="showModal">打开弹窗</button> <Modal ref="modalRef"> <h2>弹窗内容</h2> </Modal> </template>

总结

组件通信技巧汇总:

方式方向场景
Props父→子传递数据
Emit子→父传递事件
v-model双向表单组件
插槽父→子内容分发
作用域插槽子→父子组件数据供父组件渲染
defineExpose父→子调用子组件方法

下一篇聊聊全局样式与 CSS 模块,让你的样式管理更规范。

相关文章

入门篇三:Nuxt4组件自动导入:写代码少敲一半字

入门篇二:Nuxt 4路由自动生成:告别手动配置路由的日子

延伸阅读

nuxt4完整系列,持续更新中。。,欢迎来逛逛


内容有帮助?点赞、收藏、关注三连!评论区等你 💪

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/5 23:01:13

【仅开放72小时】AI可观测性实战工作坊精华浓缩:从LangChain Trace断点注入,到Docker+K8s+Triton混合环境指标对齐(含可运行Notebook)

第一章&#xff1a;AI原生软件研发的可观测性实践 2026奇点智能技术大会(https://ml-summit.org) AI原生软件不同于传统应用&#xff0c;其可观测性需覆盖模型生命周期全链路——从训练数据漂移、推理延迟突增&#xff0c;到提示词注入攻击与LLM输出幻觉&#xff0c;均需结构化…

作者头像 李华
网站建设 2026/5/5 23:03:53

Afterpay Sandbox账号创建过程

Afterpay&#xff08;也就是 Afterpay / Clearpay&#xff09; 的 Sandbox 创建方式和 PayPal、Klarna 有点不同——它没有完全自助的“随便注册就能用”的 Sandbox&#xff0c;而是偏向 商户集成流程驱动。 我给你一步步讲清楚&#x1f447; &#x1f7e0; 1️⃣ Afterpay Sa…

作者头像 李华
网站建设 2026/5/5 23:03:19

Genymotion模拟器安装与配置全攻略:从零开始搭建高效Android开发环境

1. 为什么选择Genymotion模拟器 如果你正在开发Android应用&#xff0c;肯定知道测试环节有多重要。官方模拟器虽然稳定&#xff0c;但那个启动速度和卡顿简直让人抓狂。我最早用Android Studio自带的模拟器&#xff0c;每次启动都要等上几分钟&#xff0c;调试时还经常卡死。后…

作者头像 李华
网站建设 2026/5/5 23:04:02

终极RPG Maker插件组合:400+免费插件全面提升游戏开发效率

终极RPG Maker插件组合&#xff1a;400免费插件全面提升游戏开发效率 【免费下载链接】RPGMakerMV RPGツクールMV、MZで動作するプラグインです。 项目地址: https://gitcode.com/gh_mirrors/rp/RPGMakerMV RPGMakerMV插件集合是一个专为RPG Maker MV和MZ游戏引擎设计的…

作者头像 李华