vue 2.6 中 slot 的新用法

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

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

若是你是Vue的新手,或者尚未看到2.6版的变化,请继续阅读。也许学习插槽的最佳资源是Vue本身的文档,可是我将在这里给出一个纲要。vue

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

插槽是什么?

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

// frame.vue
<template>
  <div class="frame">
    <slot></slot>
  </div>
</template>

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

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

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

// frame.vue
<template>
  <div class="frame">
    <slot>若是这里没有指定任何内容,这就是默认内容</slot>
  </div>
</template>

因此如今若是咱们这样使用它:app

// app.vue
<template>
  <frame />
</template>

若是这里没有指定任何内容,这就是默认内容”是默认内容,可是若是像之前那样使用它,默认文本将被img标记覆盖。框架

多个/命名的插槽

能够向组件添加多个插槽,可是若是这样作了,那么除了其中一个以外,其余全部插槽都须要有名称。若是有一个没有名称的槽,它就是默认槽。下面是如何建立多个插槽:ide

// titled-frame.vue
<template>
  <div class="frame">
    <header><h2>
      <slot name="header">Title</slot>
    </h2></header>
    <slot>若是这里没有指定任何内容,这就是默认内容</slot>
  </div>
</template>

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

// app.vue
<template>
  <titled-frame>
    <template v-slot:header>
      <!-- The code below goes into the header slot -->
      My Image’s Title
    </template>
    <!-- The code below goes into the default slot -->
    <img src="an-image.jpg">
  </titled-frame>
</template>

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

注意,v-slotVue 2.6的新版本,因此若是你使用的是旧版本,则须要阅读关于不推荐的slot语法的文档。

做用域插槽

还须要知道的另外一件事是插槽能够将数据/函数传递给他们的孩子。 为了证实这一点,咱们须要一个彻底不一样的带有插槽的示例组件:建立一个组件,该组件将当前用户的数据提供给其插槽:

// current-user.vue
<template>
  <span>
    <slot v-bind:user="user">
      {{ user.lastName }}
    </slot>
  </span>
</template>

<script>
export default {
  data () {
    return {
      user: ...
    }
  }
}
</script>

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

// app.vue
<template>
  <current-user>
    <template v-slot:default="slotProps">{{ slotProps.user.firstName }}</template>    
  </current-user>
</template>

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

这里有几点须要注意:

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

因此,上面的例子能够这样重写

// app.vue
<template>
  <current-user v-slot="{user}">
    {{ user.firstName }}
  </current-user>
</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,所以咱们应该使用一个插槽。

<!-- my-button.vue -->
<template>
  <button class="btn btn-primary btn-lg">
    <slot>Click Me!</slot>
  </button>
</template>

如今咱们能够在任何地方使用它,不管你想要什么内容

<!-- 使用 my-button.vue -->
<template>
  <my-button>
    <img src="/img/awesome-icon.jpg"> 我是小智!
  </my-button>
</template>

固然,你能够选择比按钮更大的东西。 坚持使用Bootstrap,让咱们看一个模态:

<!-- my-modal.vue -->
<template>
<div class="modal" tabindex="-1" role="dialog">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <slot name="header"></slot>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">×</span>
        </button>
      </div>
      <div class="modal-body">
        <slot name="body"></slot>
      </div>
      <div class="modal-footer">
        <slot name="footer"></slot>
      </div>
    </div>
  </div>
</div>
</template>

如今,使用它:

<!-- 使用 my-modal.vue -->
<template>
  <my-modal>
    <template #header>
      <h5>你们最棒!</h5>
    </template>
    <template #body>
      <p>你们加油</p>
    </template>
    <template #footer>
      <em>你们好样的!</em>
    </template>
  </my-modal>
</template>

上述类型的插槽用例显然很是有用,但它能够作得更多。

复用函数

Vue组件并不彻底是关于HTML和CSS的。它们是用JavaScript构建的,因此也是关于函数的。插槽对于一次性建立函数并在多个地方使用功能很是有用。让咱们回到模态示例并添加一个关闭模态的函数

<!-- my-modal.vue -->
<template>
<div class="modal" tabindex="-1" role="dialog">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <slot name="header"></slot>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">×</span>
        </button>
      </div>
      <div class="modal-body">
        <slot name="body"></slot>
      </div>
      <div class="modal-footer">        
        <slot name="footer" :closeModal="closeModal"></slot>
      </div>
    </div>
  </div>
</div>
</template>

<script>
export default {
  //...
  methods: {
    closeModal () {
      // 关闭对话框时,须要作的事情
    }
  }
}
</script>

当使用此组件时,能够向footer添加一个能够关闭模​​态的按钮。 一般,在Bootstrap模式的状况下,能够将data-dismiss =“modal”添加到按钮来进行关闭。

但咱们但愿隐藏Bootstrap 特定的东西。 因此咱们传递给他们一个他们能够调用的函数,这样使用者就不会知道咱们有使用 Bootstrap 的东西。

<!-- 使用 my-modal.vue -->
<template>
  <my-modal>
    <template #header>
      <h5>Awesome Interruption!</h5>
    </template>
    <template #body>
      <p>你们加油!</p>
    </template>
    <template #footer="{closeModal}">
      <button @click="closeModal">
        点我能够关闭烦人的对话框
      </button>
    </template>
  </my-modal>
</template>

无渲染组件

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

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

<template>
  <transition name="fade" v-bind="$attrs" v-on="$listeners">
    <slot></slot>
  </transition>
</template>
<style>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s;
}
.fade-enter, .fade-leave-to {
  opacity: 0;
}
</style>

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

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

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

<!-- promised.vue -->
<template>
  <span>
    <slot  name="rejected"  v-if="error" :error="error"></slot>
    <slot  name="resolved"  v-else-if="resolved" :data="data"></slot>
    <slot  name="pending"  v-else></slot>
  </span>
</template>

<script>
export  default {
  props: {
    promise:  Promise
  },

  data: () => ({
    resolved:  false,
    data:  null,
    error:  null
  }),  

  watch: {
    promise: {
      handler (promise) {
        this.resolved  =  false
        this.error  =  null

        if (!promise) {
          this.data  =  null
          return
        }

        promise.then(data  => {
          this.data  =  data
          this.resolved  =  true
        })
        .catch(err  => {
          this.error  =  err
          this.resolved  =  true
        })
      },
      immediate:  true
    }
  }
}
</script>

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

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

<template>
  <div>
    <promised :promise="somePromise">
      <template #resolved="{ data }">
        Resolved: {{ data }}
      </template>
      <template #rejected="{ error }">
        Rejected: {{ error }}
      </template>
      <template #pending>
          请求中...
      </template>
    </promised>
  </div>
</template>
...

咱们将somePromise传递给无渲染组件。 而后等待它完成,对于 pending 的插槽,显示“请求中...”。 若是成功,显示“Resolved:对应的值”。 若是失败,显示“已Rejected:失败的缘由”。 如今咱们再也不须要跟踪此组件中的promise的状态,由于该部分被拉出到它本身的可重用组件中。

那么,咱们能够作些什么来绕过promised.vue中的插槽? 要删除它,咱们须要删除template部分并向咱们的组件添加render函数:

render () {
  if (this.error) {
    return this.$scopedSlots['rejected']({error: this.error})
  }

  if (this.resolved) {
    return this.$scopedSlots['resolved']({data: this.data})
  }

  return this.$scopedSlots['pending']()
}


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

总结

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

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

交流

干货系列文章汇总以下,以为不错点个Star,欢迎 加群 互相学习。

https://github.com/qq44924588...

我是小智,公众号「大迁世界」做者,对前端技术保持学习爱好者。我会常常分享本身所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,便可看到福利,你懂的。

clipboard.png

相关文章
相关标签/搜索