当前位置:首页 > 技术 > 正文内容

Vue.js 组件通信进阶:深入理解与实战插槽机制

访客 技术 2026年9月3日 1

插槽(Slots)机制概述

在 Vue.js 的组件化架构中,Props 主要负责向下传递状态数据,而插槽(Slots)则专门用于处理内容分发(Content Distribution)。通过插槽,父组件可以将自定义的 HTML 模板注入到子组件的特定位置,从而极大地提升组件的复用性和灵活性。根据业务场景的复杂度,插槽主要分为三种类型:默认插槽、具名插槽和作用域插槽。

默认插槽(Default Slots)

默认插槽是最基础的用法,适用于子组件中只需要一个内容注入点的场景。父组件在调用子组件时,标签内部的所有内容都会被分发到子组件的 <slot> 位置。

样式作用域规则:插槽内容是在父组件的作用域中编译的,因此父组件中定义的样式会直接作用于这些内容。如果子组件使用了 scoped 样式,则无法直接修改插槽内容的样式,除非使用深度选择器(如 :deep())。

父组件:分发内容

<template>
  <div class="product-grid">
    <!-- 传入图片结构,样式受父组件控制 -->
    <ProductCard category-name="智能手机" :items="phones">
      <img src="https://via.placeholder.com/300x200?text=Smartphones" alt="智能手机展示">
    </ProductCard>

    <!-- 传入列表结构 -->
    <ProductCard category-name="笔记本电脑" :items="laptops">
      <ul class="item-list">
        <li v-for="item in laptops" :key="item.id">{{ item.name }}</li>
      </ul>
    </ProductCard>

    <!-- 传入视频结构 -->
    <ProductCard category-name="智能穿戴" :items="wearables">
      <video src="https://example.com/wearable-demo.mp4" controls></video>
    </ProductCard>

    <!-- 未传入内容,将显示子组件中的后备(Fallback)内容 -->
    <ProductCard></ProductCard>
  </div>
</template>

<script>
import ProductCard from "./components/ProductCard.vue";

export default {
  name: "App",
  components: { ProductCard },
  data() {
    return {
      phones: [{ id: 1, name: 'iPhone 15' }, { id: 2, name: 'Galaxy S24' }],
      laptops: [{ id: 1, name: 'MacBook Pro' }, { id: 2, name: 'ThinkPad X1' }],
      wearables: [{ id: 1, name: 'Apple Watch' }, { id: 2, name: 'Mi Band' }]
    };
  }
};
</script>

<style>
.product-grid {
  display: flex;
  gap: 20px;
  padding: 20px;
}
img, video {
  width: 100%;
  border-radius: 8px;
}
.item-list {
  list-style-type: none;
  padding: 0;
}
</style>

子组件:定义插槽与后备内容

<template>
  <div class="card">
    <h3>{{ categoryName }} 专区</h3>
    <!-- 定义默认插槽,标签内的内容为后备内容(Fallback) -->
    <slot>
      <p class="empty-state">暂无商品信息,请稍后再试。</p>
    </slot>
  </div>
</template>

<script>
export default {
  name: "ProductCard",
  props: {
    categoryName: { type: String, default: '精选商品' },
    items: { type: Array, default: () => [] }
  }
};
</script>

<style scoped>
.card {
  background-color: #f9f9f9;
  border: 1px solid #e0e0e0;
  border-radius: 12px;
  width: 250px;
  padding: 15px;
  box-shadow: 0 4px 6px rgba(0,0,0,0.05);
}
h3 {
  text-align: center;
  color: #333;
  margin-bottom: 15px;
  padding-bottom: 10px;
  border-bottom: 2px solid #007bff;
}
.empty-state {
  color: #999;
  text-align: center;
  font-style: italic;
}
</style>

具名插槽(Named Slots)

当子组件需要多个独立的内容注入点时(例如卡片的头部、主体和底部),就需要使用具名插槽。通过在 <slot> 标签上定义 name 属性,父组件可以使用 v-slot 指令(或其简写 #)将不同的模板片段精准分发到对应的插槽中。

若需要将多个 DOM 元素放入同一个具名插槽,应使用 <template> 标签进行包裹,这样可以避免生成多余的 DOM 节点。

子组件:定义多个具名插槽

<template>
  <div class="card">
    <header class="card-header">
      <slot name="header">
        <h3>{{ categoryName }} 默认标题</h3>
      </slot>
    </header>
    
    <main class="card-body">
      <slot name="body"></slot>
    </main>
    
    <footer class="card-footer">
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

<script>
export default {
  name: "ProductCard",
  props: ['categoryName', 'items']
};
</script>

父组件:使用 v-slot 分发内容

<template>
  <div class="product-grid">
    <ProductCard category-name="智能手机">
      <!-- 使用简写 #header 替代 v-slot:header -->
      <template #header>
        <h3>📱 热门智能手机</h3>
      </template>

      <template #body>
        <img src="https://via.placeholder.com/300x200?text=Phones" alt="手机">
      </template>

      <template #footer>
        <a href="/phones">查看更多手机 &gt;</a>
      </template>
    </ProductCard>

    <ProductCard category-name="笔记本电脑">
      <template #header>
        <h3>💻 生产力工具</h3>
      </template>

      <!-- 多个元素包裹在 template 中 -->
      <template #footer>
        <button class="btn">游戏本</button>
        <button class="btn">轻薄本</button>
      </template>
    </ProductCard>
  </div>
</template>

作用域插槽(Scoped Slots)

默认情况下,插槽内容无法访问子组件内部的状态。作用域插槽打破了这一限制,允许子组件通过 <slot> 标签将自身的数据作为属性传递给父组件。父组件在接收时,可以通过 ES6 的解构赋值语法直接获取这些数据,从而实现"数据由子组件提供,渲染逻辑由父组件决定"的高级解耦模式。

子组件:暴露内部数据

<template>
  <div class="card">
    <h3>{{ categoryName }} 专区</h3>
    <!-- 将 items 数组和 discount 状态传递给父组件 -->
    <slot :products="items" discount="0.85" status="on-sale">
      <p>加载中...</p>
    </slot>
  </div>
</template>

<script>
export default {
  name: "ProductCard",
  props: ['categoryName', 'items']
};
</script>

父组件:接收并渲染作用域数据

<template>
  <div class="product-grid">
    <!-- 场景一:常规对象接收 -->
    <ProductCard category-name="智能手机" :items="phones">
      <template #default="slotProps">
        <ul>
          <li v-for="product in slotProps.products" :key="product.id">
            {{ product.name }} - 折扣: {{ slotProps.discount }}
          </li>
        </ul>
      </template>
    </ProductCard>

    <!-- 场景二:使用 ES6 解构赋值(推荐) -->
    <ProductCard category-name="笔记本电脑" :items="laptops">
      <template #default="{ products, status }">
        <div class="product-list">
          <div v-for="product in products" :key="product.id" class="product-item">
            <span>{{ product.name }}</span>
            <span v-if="status === 'on-sale'" class="badge">促销中</span>
          </div>
        </div>
      </template>
    </ProductCard>

    <!-- 场景三:配合具名插槽使用作用域 -->
    <ProductCard category-name="智能穿戴" :items="wearables">
      <template #body="{ products }">
        <h4>穿戴设备列表</h4>
        <p v-for="product in products" :key="product.id">{{ product.name }}</p>
      </template>
    </ProductCard>
  </div>
</template>

<script>
import ProductCard from "./components/ProductCard.vue";

export default {
  name: "App",
  components: { ProductCard },
  data() {
    return {
      phones: [{ id: 1, name: 'iPhone 15' }, { id: 2, name: 'Galaxy S24' }],
      laptops: [{ id: 1, name: 'MacBook Pro' }, { id: 2, name: 'ThinkPad X1' }],
      wearables: [{ id: 1, name: 'Apple Watch' }, { id: 2, name: 'Mi Band' }]
    };
  }
};
</script>

<style>
.badge {
  background-color: #ff4d4f;
  color: white;
  padding: 2px 6px;
  border-radius: 4px;
  font-size: 12px;
  margin-left: 8px;
}
</style>
返回列表

上一篇:2023年楚慧杯(DASCTF)竞赛解题记录

没有最新的文章了...

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。