当前位置: 首页 > news >正文

15 VUE学习:插槽slot

插槽内容与出口

这个也是插槽
在这里插入图片描述

组件能够接收任意类型的 JavaScript 值作为 props,但组件要如何接收模板内容呢?在某些场景中,我们可能想要为子组件传递一些模板片段,让子组件在它们的组件中渲染这些片段。

举例来说,这里有一个 <FancyButton> 组件,可以像这样使用:

<FancyButton>Click me! <!-- 插槽内容 -->
</FancyButton>

<FancyButton> 的模板是这样的:

<button class="fancy-btn"><slot></slot> <!-- 插槽出口 -->
</button>

<slot> 元素是一个插槽出口 (slot outlet),标示了父元素提供的插槽内容 (slot content) 将在哪里被渲染。

插槽图示

最终渲染出的 DOM 是这样:

<button class="fancy-btn">Click me!</button>

通过使用插槽,<FancyButton> 仅负责渲染外层的 <button> (以及相应的样式),而其内部的内容由父组件提供。

理解插槽的另一种方式是和下面的 JavaScript 函数作类比,其概念是类似的:

// 父元素传入插槽内容
FancyButton('Click me!')// FancyButton 在自己的模板中渲染插槽内容
function FancyButton(slotContent) {return `<button class="fancy-btn">${slotContent}</button>`
}

插槽内容可以是任意合法的模板内容,不局限于文本。例如我们可以传入多个元素,甚至是组件:

<FancyButton><span style="color:red">Click me!</span><AwesomeIcon name="plus" />
</FancyButton>

通过使用插槽,<FancyButton> 组件更加灵活和具有可复用性。现在组件可以用在不同的地方渲染各异的内容,但同时还保证都具有相同的样式。

Vue 组件的插槽机制是受[原生 Web Component `` 元素]的启发而诞生,同时还做了一些功能拓展,这些拓展的功能我们后面会学习到。

渲染作用域

插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的。举例来说:

<span>{{ message }}</span>
<FancyButton>{{ message }}</FancyButton>

这里的两个 {{ message }} 插值表达式渲染的内容都是一样的。

插槽内容无法访问子组件的数据。Vue 模板中的表达式只能访问其定义时所处的作用域,这和 JavaScript 的词法作用域规则是一致的。换言之:

父组件模板中的表达式只能访问父组件的作用域;子组件模板中的表达式只能访问子组件的作用域。

默认内容

在外部没有提供任何内容的情况下,可以为插槽指定默认内容。比如有这样一个 <SubmitButton> 组件:

<button type="submit"><slot></slot>
</button>

如果我们想在父组件没有提供任何插槽内容时在 <button> 内渲染“Submit”,只需要将“Submit”写在 <slot> 标签之间来作为默认内容:

<button type="submit"><slot>Submit <!-- 默认内容 --></slot>
</button>

现在,当我们在父组件中使用 <SubmitButton> 且没有提供任何插槽内容时:

<SubmitButton />

“Submit”将会被作为默认内容渲染:

<button type="submit">Submit</button>

但如果我们提供了插槽内容:

<SubmitButton>Save</SubmitButton>

那么被显式提供的内容会取代默认内容:

<button type="submit">Save</button>

具名插槽

有时在一个组件中包含多个插槽出口是很有用的。举例来说,在一个 <BaseLayout> 组件中,有如下模板:

<div class="container"><header><!-- 标题内容放这里 --></header><main><!-- 主要内容放这里 --></main><footer><!-- 底部内容放这里 --></footer>
</div>

对于这种场景,<slot> 元素可以有一个特殊的 attribute name,用来给各个插槽分配唯一的 ID,以确定每一处要渲染的内容:

<div class="container"><header><slot name="header"></slot></header><main><slot></slot></main><footer><slot name="footer"></slot></footer>
</div>

这类带 name 的插槽被称为具名插槽 (named slots)。没有提供 name<slot> 出口会隐式地命名为“default”。

在父组件中使用 <BaseLayout> 时,我们需要一种方式将多个插槽内容传入到各自目标插槽的出口。此时就需要用到具名插槽了:

要为具名插槽传入内容,我们需要使用一个含 v-slot 指令的 <template> 元素,并将目标插槽的名字传给该指令:

<BaseLayout><template v-slot:header><!-- header 插槽的内容放这里 --></template>
</BaseLayout>

v-slot 有对应的简写 #,因此 <template v-slot:header> 可以简写为 <template #header>。其意思就是“将这部分模板片段传入子组件的 header 插槽中”。

具名插槽图示

下面我们给出完整的、向 传递插槽内容的代码,指令均使用的是缩写形式:

<BaseLayout><template #header><h1>Here might be a page title</h1></template><template #default><p>A paragraph for the main content.</p><p>And another one.</p></template><template #footer><p>Here's some contact info</p></template>
</BaseLayout>

当一个组件同时接收默认插槽和具名插槽时,所有位于顶级的非 <template> 节点都被隐式地视为默认插槽的内容。所以上面也可以写成:

<BaseLayout><template #header><h1>Here might be a page title</h1></template><!-- 隐式的默认插槽 --><p>A paragraph for the main content.</p><p>And another one.</p><template #footer><p>Here's some contact info</p></template>
</BaseLayout>

现在 <template> 元素中的所有内容都将被传递到相应的插槽。最终渲染出的 HTML 如下:

<div class="container"><header><h1>Here might be a page title</h1></header><main><p>A paragraph for the main content.</p><p>And another one.</p></main><footer><p>Here's some contact info</p></footer>
</div>

使用 JavaScript 函数来类比可能更有助于你来理解具名插槽:

// 传入不同的内容给不同名字的插槽
BaseLayout({header: `...`,default: `...`,footer: `...`
})// <BaseLayout> 渲染插槽内容到对应位置
function BaseLayout(slots) {return `<div class="container"><header>${slots.header}</header><main>${slots.default}</main><footer>${slots.footer}</footer></div>`
}

条件插槽

有时你需要根据插槽是否存在来渲染某些内容。

你可以结合使用 [$slots]属性与 [v-if]来实现。

在下面的示例中,我们定义了一个卡片组件,它拥有三个条件插槽:headerfooterdefault。 当 header、footer 或 default 存在时,我们希望包装它们以提供额外的样式:

<template><div class="card"><div v-if="$slots.header" class="card-header"><slot name="header" /></div><div v-if="$slots.default" class="card-content"><slot /></div><div v-if="$slots.footer" class="card-footer"><slot name="footer" /></div></div>
</template>

动态插槽名

<base-layout><template v-slot:[dynamicSlotName]>...</template><!-- 缩写为 --><template #[dynamicSlotName]>...</template>
</base-layout>

注意这里的表达式和动态指令参数受相同的[语法限制]。

作用域插槽

在上面的[渲染作用域]中我们讨论到,插槽的内容无法访问到子组件的状态。

然而在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。要做到这一点,我们需要一种方法来让子组件在渲染时将一部分数据提供给插槽。

我们也确实有办法这么做!可以像对组件传递 props 那样,向一个插槽的出口上传递 attributes:

<!-- <MyComponent> 的模板 -->
<div><slot :text="greetingMessage" :count="1"></slot>
</div>

当需要接收插槽 props 时,默认插槽和具名插槽的使用方式有一些小区别。下面我们将先展示默认插槽如何接受 props,通过子组件标签上的 v-slot 指令,直接接收到了一个插槽 props 对象:

<MyComponent v-slot="slotProps">{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>

无渲染组件

上面的 <FancyList> 案例同时封装了可重用的逻辑 (数据获取、分页等) 和视图输出,但也将部分视图输出通过作用域插槽交给了消费者组件来管理。

如果我们将这个概念拓展一下,可以想象的是,一些组件可能只包括了逻辑而不需要自己渲染内容,视图输出通过作用域插槽全权交给了消费者组件。我们将这种类型的组件称为无渲染组件

这里有一个无渲染组件的例子,一个封装了追踪当前鼠标位置逻辑的组件:

<MouseTracker v-slot="{ x, y }">Mouse is at: {{ x }}, {{ y }}
</MouseTracker>

虽然这个模式很有趣,但大部分能用无渲染组件实现的功能都可以通过组合式 API 以另一种更高效的方式实现,并且还不会带来额外组件嵌套的开销。之后我们会在组合式函数一章中介绍如何更高效地实现追踪鼠标位置的功能。

尽管如此,作用域插槽在需要同时封装逻辑、组合视图界面时还是很有用,就像上面的 <FancyList> 组件那样。

关注我,不迷路,共学习,同进步

关注我,不迷路,共学习,同进步

在这里插入图片描述

相关文章:

  • leetcode刷题
  • 数据库连接项目
  • 池的概念以及数据库连接池 Druid
  • 深入理解 Mysql 分层架构:从存储引擎到查询优化器的内部机制解析
  • 1738. 找出第 K 大的异或坐标值
  • 嵌入式进阶——舵机控制PWM
  • 辐射度技术在AI去衣中的魅力与科学
  • 基于长短期记忆网络 LSTM 的送餐时间预测
  • 1960-2022年世界银行WDI面板数据(1400+指标)
  • 论文阅读--ActionCLIP
  • Leetcode.560.和为k的子数组
  • python基础(1) -- 基本数据类型与变量
  • SQL Server--死锁
  • SDK——如何快速上手一个接口驱动任务(以iic为例)
  • 基于springboot+vue的4S店车辆管理系统
  • 2017前端实习生面试总结
  • CentOS 7 修改主机名
  • exif信息对照
  • gitlab-ci配置详解(一)
  • javascript面向对象之创建对象
  • JavaScript设计模式之工厂模式
  • laravel with 查询列表限制条数
  • Solarized Scheme
  • SpingCloudBus整合RabbitMQ
  • Storybook 5.0正式发布:有史以来变化最大的版本\n
  • use Google search engine
  • Web标准制定过程
  • 官方新出的 Kotlin 扩展库 KTX,到底帮你干了什么?
  • 基于阿里云移动推送的移动应用推送模式最佳实践
  • 记录一下第一次使用npm
  • 开源SQL-on-Hadoop系统一览
  • 如何将自己的网站分享到QQ空间,微信,微博等等
  • 使用阿里云发布分布式网站,开发时候应该注意什么?
  • 听说你叫Java(二)–Servlet请求
  • 通过获取异步加载JS文件进度实现一个canvas环形loading图
  • 通过来模仿稀土掘金个人页面的布局来学习使用CoordinatorLayout
  • 延迟脚本的方式
  • 再谈express与koa的对比
  • Nginx实现动静分离
  • Prometheus VS InfluxDB
  • 浅谈sql中的in与not in,exists与not exists的区别
  • # 日期待t_最值得等的SUV奥迪Q9:空间比MPV还大,或搭4.0T,香
  • #Linux杂记--将Python3的源码编译为.so文件方法与Linux环境下的交叉编译方法
  • #常见电池型号介绍 常见电池尺寸是多少【详解】
  • #数据结构 笔记三
  • #我与Java虚拟机的故事#连载01:人在JVM,身不由己
  • #中国IT界的第一本漂流日记 传递IT正能量# 【分享得“IT漂友”勋章】
  • $LayoutParams cannot be cast to android.widget.RelativeLayout$LayoutParams
  • (11)(2.1.2) DShot ESCs(四)
  • (DFS + 剪枝)【洛谷P1731】 [NOI1999] 生日蛋糕
  • (k8s)kubernetes 部署Promehteus学习之路
  • (安卓)跳转应用市场APP详情页的方式
  • (二)PySpark3:SparkSQL编程
  • (附源码)springboot美食分享系统 毕业设计 612231
  • (附源码)ssm基于jsp的在线点餐系统 毕业设计 111016