为了保证的可读性,本文采用意译而非直译。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-slot
是Vue 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'
。
为了简单起见,如今让咱们假设你的应用使用btn
、btn-primary
和btn-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 成功完成或失败时更新状态。
而后,在模板中,咱们根据状态显示一个不一样的槽。请注意,咱们没有保持它真正的无渲染,由于咱们须要一个根元素来使用模板。咱们还将data
和error
传递到相关的插槽范围。
<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...
我是小智,公众号「大迁世界」做者,对前端技术保持学习爱好者。我会常常分享本身所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复福利,便可看到福利,你懂的。