在Vue中提供了一个内置组件:「slot」,官方称之为「插槽」。其作用主要是为了做内容分发。内容分发这个词理解起来可能不太直观,如果学习过Angular,就可以将它理解为Angular中的ng-content,ng-content的解释是「内容投影」,就将内容投影到组件的ng-content中。slot和它的作用是一样的。
在官方中给出了通过插槽分发内容的例子。首先定义一个组件,在组件模板中使用「slot」标记插槽在模板中的位置:
Vue.component('alert-box', { template: ` <div class="demo-alert-box"> <strong>Error!</strong> <slot></slot> </div> ` })使用自定义组件:
<alert-box> Something bad happened. </alert-box>其中, “Something bad happened.”作为内容被分发到自定义组件中的「slot」处,效果如下:
<div class="demo-alert-box"> <strong>Error!</strong> Something bad happened. </div>这样我们就通过使用「slot」完成了内容分发。
slot的应用场景有很多,如通过slot实现统一的布局、结构、效果等。在 Element UI中就有许多使用slot的组件。如表单中的FormItem:
<template> <div class="el-form-item" :class="[{ 'el-form-item--feedback': elForm && elForm.statusIcon, 'is-error': validateState === 'error', 'is-validating': validateState === 'validating', 'is-success': validateState === 'success', 'is-required': isRequired || required, 'is-no-asterisk': elForm && elForm.hideRequiredAsterisk }, sizeClass ? 'el-form-item--' + sizeClass : '' ]"> <label-wrap :is-auto-width="labelStyle && labelStyle.width === 'auto'" :update-all="form.labelWidth === 'auto'"> <label :for="labelFor" class="el-form-item__label" :style="labelStyle" v-if="label || $slots.label"> <slot name="label">{{label + form.labelSuffix}}</slot> </label> </label-wrap> <div class="el-form-item__content" :style="contentStyle"> <slot></slot> <transition name="el-zoom-in-top"> <slot v-if="validateState === 'error' && showMessage && form.showMessage" name="error" :error="validateMessage"> <div class="el-form-item__error" :class="{ 'el-form-item__error--inline': typeof inlineMessage === 'boolean' ? inlineMessage : (elForm && elForm.inlineMessage || false) }" > {{validateMessage}} </div> </slot> </transition> </div> </div> </template>在FormItem组件中,使用到了3个slot,从上而下它们的作用分别是:
label内容分发
默认内容分发
错误内容分发
在FormItem的例子中,发现在slot中使用到了name属性。name是用来命名插槽的。在模板中通过name来区分多个插槽。
<div class="container"> <header> <!-- 我们希望把页头放这里 --> </header> <main> <!-- 我们希望把主要内容放这里 --> </main> <footer> <!-- 我们希望把页脚放这里 --> </footer> </div>要实现上面的效果,就可以使用具名插槽:
<div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div>不带name属性的插槽,会带有隐含的名字“default”。使用如下:
<base-layout> <template v-slot:header> <h1>Here might be a page title</h1> </template> <p>A paragraph for the main content.</p> <p>And another one.</p> <template v-slot:footer> <p>Here's some contact info</p> </template> </base-layout>渲染效果如下:
<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>具名插槽的使用时的v-slot可以缩写为:
<base-layout> <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> </base-layout>在实际开发过程中,很多时候需要将组件内的数据通过slot的方式提供给外部使用,此时,就是通过slot属性的方式将数据传递到组件外:current-user
<span> <slot v-bind:user="user"> {{ user.lastName }} </slot> </span>使用时:
<current-user> <template v-slot:default="slotProps"> {{ slotProps.user.firstName }} </template> </current-user>只有默认插槽时,我们可以对上面的使用方式进行简化:
<current-user v-slot:default="slotProps"> {{ slotProps.user.firstName }} </current-user>不带参数的slot默认为default,所以还可以简化为:
<current-user v-slot="slotProps"> {{ slotProps.user.firstName }} </current-user>在ES中,提供了对象解构功能,所以我们也可以使用解构的方式来处理slot:
<current-user v-slot="{ user }"> {{ user.firstName }} </current-user>此处可以理解为:
let { user } = slotProps通过解构的方式,将slotProps的user属性提取出来。
「slot」的内容分发功能,可以帮助开发者更加方便的实现布局结构组件化的复用。同样也是开发者进阶的一个标志。