摘要: 理解Vue插槽。

Fundebug经授权转载,版权归原作者所有。

为了保证的可读性,本文采用意译而非直译。

最近发布不久的Vue 2.6,使用插槽的语法变得更加简洁。 对插槽的这种改变让我对发现插槽的潜在功能感兴趣,以便为我们基于Vue的项目提供可重用性,新功能和更清晰的可读性。 真正有能力的插槽是什么?

如果你是Vue的新手,或者还没有看到2.6版的变化,请继续阅读。也许学习插槽的最佳资源是Vue自己的文档,但是我将在这里给出一个纲要。

想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!

插槽是什么?

插槽是Vue组件的一种机制,它允许你以一种不同于严格的父子关系的方式组合组件。插槽为你提供了一个将内容放置到新位置或使组件更通用的出口。从一个简单的例子开始:

  1. // frame.vue
  2. <template>
  3. <div class="frame">
  4. <slot></slot>
  5. </div>
  6. </template>

这个组件最外层是一个div。假设div的存在是为了围绕其内容创建一个样式框架。这个组件可以通用地用于将框架包围在wq你想要的任何内容上,来看看它是怎么用的。这里的frame组件指的是我们刚才做的组件。

  1. // app.vue
  2. <template>
  3. <frame><img src="an-image.jpg"></frame>
  4. </template>

在开始和结束frame标记之间的内容将插入到插槽所在的frame组件中,替换slot标记。这是最基本的方法。还可以简单地通过填充指定要放入槽中的默认内容

  1. // frame.vue
  2. <template>
  3. <div class="frame">
  4. <slot>如果这里没有指定任何内容,这就是默认内容</slot>
  5. </div>
  6. </template>

所以现在如果我们这样使用它:

  1. // app.vue
  2. <template>
  3. <frame />
  4. </template>

如果这里没有指定任何内容,这就是默认内容”是默认内容,但是如果像以前那样使用它,默认文本将被img标记覆盖。

多个/命名的插槽

可以向组件添加多个插槽,但是如果这样做了,那么除了其中一个之外,其他所有插槽都需要有名称。如果有一个没有名称的槽,它就是默认槽。下面是如何创建多个插槽:

  1. // titled-frame.vue
  2. <template>
  3. <div class="frame">
  4. <header><h2>
  5. <slot name="header">Title</slot>
  6. </h2></header>
  7. <slot>如果这里没有指定任何内容,这就是默认内容</slot>
  8. </div>
  9. </template>

我们保留了相同的默认槽,但这次我们添加了一个名为header的槽,可以在其中输入标题,用法如下:

  1. // app.vue
  2. <template>
  3. <titled-frame>
  4. <template v-slot:header>
  5. <!-- The code below goes into the header slot -->
  6. My Images Title
  7. </template>
  8. <!-- The code below goes into the default slot -->
  9. <img src="an-image.jpg">
  10. </titled-frame>
  11. </template>

就像之前一样,如果我们想将内容添加到默认槽中,只需将其直接放在titled-frame组件中。但是,要将内容添加到命名槽中,我们需要用v-slot指令将代码包裹在在template标记中。在v-slot之后添加冒号(:),然后写出要传递内容的slot的名称。

注意,v-slotVue 2.6的新版本,所以如果你使用的是旧版本,则需要阅读关于不推荐的slot语法的文档。

作用域插槽

还需要知道的另一件事是插槽可以将数据/函数传递给他们的孩子。 为了证明这一点,我们需要一个完全不同的带有插槽的示例组件:创建一个组件,该组件将当前用户的数据提供给其插槽:

  1. // current-user.vue
  2. <template>
  3. <span>
  4. <slot v-bind:user="user">
  5. {{ user.lastName }}
  6. </slot>
  7. </span>
  8. </template>
  9. <script>
  10. export default {
  11. data () {
  12. return {
  13. user: ...
  14. }
  15. }
  16. }
  17. </script>

该组件有一个名为user的属性,其中包含关于用户的详细信息。默认情况下,组件显示用户的姓,但请注意,它使用v-bind将用户数据绑定到slot。这样,我们就可以使用这个组件向它的后代提供用户数据

  1. // app.vue
  2. <template>
  3. <current-user>
  4. <template v-slot:default="slotProps">{{ slotProps.user.firstName }}</template>
  5. </current-user>
  6. </template>

为了访问传递给slot的数据,我们使用v-slot指令的值指定作用域变量的名称。

这里有几点需要注意:

  • 我们指定了default的名称,但是不需要为默认槽指定名称。相反,我们可以使用v-slot="slotProps"
  • 不需要使用slotProps作为名称,可以随便叫它什么。
  • 如果只使用默认槽,可以跳过内部template标记,直接将v-slot指令放到当前current-user上。
  • 可以使用对象解构来创建对作用域插槽数据的直接引用,而不是使用单个变量名。换句话说,可以使用v-slot="{user}"代替v-slot="slotProps",然后可以直接使用user而不是slotProps.user

所以,上面的例子可以这样重写

  1. // app.vue
  2. <template>
  3. <current-user v-slot="{user}">
  4. {{ user.firstName }}
  5. </current-user>
  6. </template>

还有几点要记住:

  • 可以使用v-bind指令绑定多个值。
  • 也可以将函数传递到作用域槽。许多库使用它来提供可重用的函数组件。
  • v-slot 的别名是#。因此,可以用#header="data" 来代替 v-slot:header="data"。还可以使用 #header来代替 v-slot:header(前提:不是作用域插槽时)。对于默认插槽,在使用别名时需要指定默认名称。换句话说,需要这样写 #default="data" 而不是#="data"

可以从文档中了解更多的细节,但这足以帮助你理解在本文剩下部分中讨论的内容。

你能用插槽做什么?

插槽不是为了一个目的而构建的,或者至少如果它们是,它们已经超越了最初的意图,成为做许多不同事物的强大工具。

可重用的模式

组件总是被设计为可重用的,但是某些模式对于使用单个“普通”组件来实施是不切实际的,因为为了自定义它,需要的props 数量可能过多或者需要通过props传递大部分内容或其它组件。

插槽可用包裹外部的HTML标签或者组件,并允许其他HTML或组件放在具名插槽对应名称的插槽上。

对于的第一个例子,从简单的东西开始:一个按钮。假设咱们的团队正在使用 Bootstrap。使用Bootstrap,按钮通常与基本的“btn”类和指定颜色的类绑定在一起,比如“btn-primary”。你还可以添加size类,比如'btn-lg'

为了简单起见,现在让我们假设你的应用使用btnbtn-primarybtn-lg。你不希望总是必须在按钮上写下这三个类,或者你不相信新手会记得写下这三个类。

在这种情况下,可以创建一个自动包含所有这三个类的组件,但是如何允许自定义内容? prop 不实用,因为允许按钮包含各种HTML,因此我们应该使用一个插槽。

  1. <!-- my-button.vue -->
  2. <template>
  3. <button class="btn btn-primary btn-lg">
  4. <slot>Click Me!</slot>
  5. </button>
  6. </template>

现在我们可以在任何地方使用它,无论你想要什么内容

  1. <!-- 使用 my-button.vue -->
  2. <template>
  3. <my-button>
  4. <img src="/img/awesome-icon.jpg"> 我是小智!
  5. </my-button>
  6. </template>

当然,你可以选择比按钮更大的东西。 坚持使用Bootstrap,让我们看一个模态:

  1. <!-- my-modal.vue -->
  2. <template>
  3. <div class="modal" tabindex="-1" role="dialog">
  4. <div class="modal-dialog" role="document">
  5. <div class="modal-content">
  6. <div class="modal-header">
  7. <slot name="header"></slot>
  8. <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  9. <span aria-hidden="true">×</span>
  10. </button>
  11. </div>
  12. <div class="modal-body">
  13. <slot name="body"></slot>
  14. </div>
  15. <div class="modal-footer">
  16. <slot name="footer"></slot>
  17. </div>
  18. </div>
  19. </div>
  20. </div>
  21. </template>

现在,使用它:

  1. <!-- 使用 my-modal.vue -->
  2. <template>
  3. <my-modal>
  4. <template #header>
  5. <h5>大家最棒!</h5>
  6. </template>
  7. <template #body>
  8. <p>大家加油</p>
  9. </template>
  10. <template #footer>
  11. <em>大家好样的!</em>
  12. </template>
  13. </my-modal>
  14. </template>

上述类型的插槽用例显然非常有用,但它可以做得更多。

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

复用函数

Vue组件并不完全是关于HTML和CSS的。它们是用JavaScript构建的,所以也是关于函数的。插槽对于一次性创建函数并在多个地方使用功能非常有用。让我们回到模态示例并添加一个关闭模态的函数

  1. <!-- my-modal.vue -->
  2. <template>
  3. <div class="modal" tabindex="-1" role="dialog">
  4. <div class="modal-dialog" role="document">
  5. <div class="modal-content">
  6. <div class="modal-header">
  7. <slot name="header"></slot>
  8. <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  9. <span aria-hidden="true">×</span>
  10. </button>
  11. </div>
  12. <div class="modal-body">
  13. <slot name="body"></slot>
  14. </div>
  15. <div class="modal-footer">
  16. <slot name="footer" :closeModal="closeModal"></slot>
  17. </div>
  18. </div>
  19. </div>
  20. </div>
  21. </template>
  22. <script>
  23. export default {
  24. //...
  25. methods: {
  26. closeModal () {
  27. // 关闭对话框时,需要做的事情
  28. }
  29. }
  30. }
  31. </script>

当使用此组件时,可以向footer添加一个可以关闭模态的按钮。 通常,在Bootstrap模式的情况下,可以将data-dismiss =“modal”添加到按钮来进行关闭。

但我们希望隐藏Bootstrap 特定的东西。 所以我们传递给他们一个他们可以调用的函数,这样使用者就不会知道我们有使用 Bootstrap 的东西。

  1. <!-- 使用 my-modal.vue -->
  2. <template>
  3. <my-modal>
  4. <template #header>
  5. <h5>Awesome Interruption!</h5>
  6. </template>
  7. <template #body>
  8. <p>大家加油!</p>
  9. </template>
  10. <template #footer="{closeModal}">
  11. <button @click="closeModal">
  12. 点我可以关闭烦人的对话框
  13. </button>
  14. </template>
  15. </my-modal>
  16. </template>

无渲染组件

最后,可以利用你所知道的关于使用插槽来传递可重用函数的知识,并剥离所有HTML,只使用插槽。这就是无渲染组件的本质:一个只提供函数而不包含任何HTML的组件。

使组件真正无渲染可能有点棘手,因为需要编写render函数而不是使用模板来消除对根元素的依赖,但它可能并不总是必要的。 来看看一个先使用模板的简单示例:

  1. <template>
  2. <transition name="fade" v-bind="$attrs" v-on="$listeners">
  3. <slot></slot>
  4. </transition>
  5. </template>
  6. <style>
  7. .fade-enter-active,
  8. .fade-leave-active {
  9. transition: opacity 0.3s;
  10. }
  11. .fade-enter, .fade-leave-to {
  12. opacity: 0;
  13. }
  14. </style>

这是一个无渲染组件的奇怪例子,因为它甚至没有任何JavaScript。这主要是因为我们正在创建一个内置无渲染函数的预配置可重用版本:transition

是的,Vue有内置的无渲染组件。这个特殊的例子取自Cristi Jora的一篇关于可重用transition的文章,展示了一种创建无渲染组件的简单方法,该组件可以标准化整个应用程序中使用的 transition

对于我们的另一个示例,我们将创建一个组件来处理切换 Promise 的不同状态中显示的内容: pending、resolved 和 failed。这是一种常见的模式,虽然它不需要很多代码,但是如果没有为了可重用性而提取逻辑,它会使很多组件变得混乱。

  1. <!-- promised.vue -->
  2. <template>
  3. <span>
  4. <slot name="rejected" v-if="error" :error="error"></slot>
  5. <slot name="resolved" v-else-if="resolved" :data="data"></slot>
  6. <slot name="pending" v-else></slot>
  7. </span>
  8. </template>
  9. <script>
  10. export default {
  11. props: {
  12. promise: Promise
  13. },
  14. data: () => ({
  15. resolved: false,
  16. data: null,
  17. error: null
  18. }),
  19. watch: {
  20. promise: {
  21. handler (promise) {
  22. this.resolved = false
  23. this.error = null
  24. if (!promise) {
  25. this.data = null
  26. return
  27. }
  28. promise.then(data => {
  29. this.data = data
  30. this.resolved = true
  31. })
  32. .catch(err => {
  33. this.error = err
  34. this.resolved = true
  35. })
  36. },
  37. immediate: true
  38. }
  39. }
  40. }
  41. </script>

这是怎么回事,小老弟?首先,请注意,该组件接收一个Promise 类型参数。在watch部分中,监听promise的变化,当promise发生变化时,清除状态,然后调用 then 并 catch promise,当 promise 成功完成或失败时更新状态。

然后,在模板中,我们根据状态显示一个不同的槽。请注意,我们没有保持它真正的无渲染,因为我们需要一个根元素来使用模板。我们还将dataerror传递到相关的插槽范围。

  1. <template>
  2. <div>
  3. <promised :promise="somePromise">
  4. <template #resolved="{ data }">
  5. Resolved: {{ data }}
  6. </template>
  7. <template #rejected="{ error }">
  8. Rejected: {{ error }}
  9. </template>
  10. <template #pending>
  11. 请求中...
  12. </template>
  13. </promised>
  14. </div>
  15. </template>
  16. ...

我们将somePromise传递给无渲染组件。 然后等待它完成,对于 pending 的插槽,显示“请求中...”。 如果成功,显示“Resolved:对应的值”。 如果失败,显示“已Rejected:失败的原因”。 现在我们不再需要跟踪此组件中的promise的状态,因为该部分被拉出到它自己的可重用组件中。

那么,我们可以做些什么来绕过promised.vue中的插槽? 要删除它,我们需要删除template部分并向我们的组件添加render函数:

  1. render () {
  2. if (this.error) {
  3. return this.$scopedSlots['rejected']({error: this.error})
  4. }
  5. if (this.resolved) {
  6. return this.$scopedSlots['resolved']({data: this.data})
  7. }
  8. return this.$scopedSlots['pending']()
  9. }

这里没有什么太复杂的。我们只是使用一些if块来查找状态,然后返回正确的作用域slot(通过this.$ scopedslot ['SLOTNAME'](…)),并将相关数据传递到slot作用域。当你不使用模板时,可以跳过使用.vue文件扩展名,方法是将JavaScript从script标记中提取出来,然后将其放入.js文件中。在编译这些Vue文件时,这应该会给你带来非常小的性能提升。

总结

Vue的插槽将基于组件的开发提升到了一个全新的水平,虽然本文已经展示了许多可以使用插槽的好方法,但还有更多的插槽。欢迎留言讨论。

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

关于Fundebug

Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了10亿+错误事件,付费客户有阳光保险、核桃编程、荔枝FM、掌门1对1、微脉、青团社等众多品牌企业。欢迎大家免费试用!

详解Vue的slot新用法的更多相关文章

  1. 详解Vue中的computed和watch

    作者:小土豆 博客园:https://www.cnblogs.com/HouJiao/ 掘金:https://juejin.cn/user/2436173500265335 1. 前言 作为一名Vue ...

  2. 详解Vue.js 技术

    本文主要从8个章节详解vue技术揭秘,小编觉得挺有用的,分享给大家. 为了把 Vue.js 的源码讲明白,课程设计成由浅入深,分为核心.编译.扩展.生态四个方面去讲,并拆成了八个章节,如下: 准备工作 ...

  3. Java 枚举(enum) 详解7种常见的用法

    Java 枚举(enum) 详解7种常见的用法 来源 https://blog.csdn.net/qq_27093465/article/details/52180865 JDK1.5引入了新的类型— ...

  4. 详解vue的数据binding原理

    自从angular火了以后,各种mv*框架喷涌而出,angular虽然比较火,但是他的坑还是蛮多的,还有许多性能问题被人们吐槽.比如坑爹的脏检查机制,数据binding是受人喜爱的,脏检查就有点…性能 ...

  5. 详解vue 路由跳转四种方式 (带参数)

    详解vue 路由跳转四种方式 (带参数):https://www.jb51.net/article/160401.htm 1.  router-link ? 1 2 3 4 5 6 7 8 9 10 ...

  6. CSS学习笔记(9)--详解CSS中:nth-child的用法

    详解CSS中:nth-child的用法 前端的哥们想必都接触过css中一个神奇的玩意,可以轻松选取你想要的标签并给与修改添加样式,是不是很给力,它就是“:nth-child”. 下面我将用几个典型的实 ...

  7. 详解Vue 方法与事件处理器

      本篇文章主要介绍了详解Vue 方法与事件处理器 ,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 方法与事件处理器 方法处理器 可以用 v-on 指令监听 DOM 事件 ...

  8. 详解Vue 如何监听Array的变化

    详解Vue 如何监听Array的变化:https://www.jb51.net/article/162584.htm

  9. 详解Java8特性之新的日期时间 API

    详解Java8特性之新的日期时间 API http://blog.csdn.net/timheath/article/details/71326329 Java8中时间日期库的20个常用使用示例 ht ...

随机推荐

  1. [Go] gocron源码阅读-go语言中的切片和类型综合

    在gocron.go文件的main函数中,有下面这一句,从这句代码中可以学习到切片和类型的综合运用 cliApp.Flags = append(cliApp.Flags, []cli.Flag{}.. ...

  2. python语法区别

    python语法区别: 大小写敏感 (动态语言:python)变量不用声明 p.s: 静态语言(Java)必须声明变量 语句末尾可以不打分号 可以直接进行数学计算 复制.粘贴功能失效,粘贴到别的地方的 ...

  3. LG2996 「USACO10NOV」Visiting Cows

    问题描述 LG2996 题解 和没有上司的舞会双倍经验? \(\mathrm{Code}\) #include<bits/stdc++.h> using namespace std; te ...

  4. luoguP5227 [AHOI2013]连通图(线性基做法)

    题意 神仙哈希做法. 随便找个生成树,给每个非树边赋一个值,树边的值为所有覆盖它的边的值得异或和. 删去边集使得图不联通当且即当边集存在一个子集异或和为0,可以用线性基. 证明的话好像画个图挺显然的 ...

  5. mybatis之<trim prefix="" suffix="" suffixOverrides="" prefixOverrides=""></trim>

    1.<trim prefix="" suffix="" suffixOverrides="" prefixOverrides=&quo ...

  6. 【转】Linux设置定时任务方法

    设置:每天4点运行脚本/var/x/web/train/modeltrain [root@T-XXX-ML-01 log]# crontab -e0 4 * * * /var/x/web/train/ ...

  7. java4wifidog_server_README

    项目地址:https://github.com/C-hill/java4wifidog_server 开发环境:Windows  JDK7  Tomcat6  Myeclipse8.5  MySQL5 ...

  8. sql语句优化的30种方法

    转载于:https://www.cnblogs.com/Little-Li/p/8031295.html 1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的 ...

  9. Python之Flask框架项目Demo入门

    Python+Flask框架项目Demo入门 本例子用到了 Flask+蓝图+Flask-Login+SQLAlchemy+WTForms+PyMySQL相关架构 Flask Web框架介绍 Flas ...

  10. fiddler 抓取winform wcf包

    修改客户端配置 <system.net> <defaultProxy> <proxy bypassonlocal="false" usesystemd ...