合规国际互联网加速 OSASE为企业客户提供高速稳定SD-WAN国际加速解决方案。 广告
# 插槽 > 本章节需要掌握组件基础 ## 插槽内容 Vue实现一套优秀的内容分发接口,基于[WEB组件草案](https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Slots-Proposal.md),使用`<slot>`标签提供内容分发服务。 这允许你像这样构成一个组件: ~~~html <todo-button> Add todo </todo-button> ~~~ `todo-button`模板应该这样写: ~~~html <!-- todo-button 组件模板 --> <button class="btn-primary"> <slot></slot> </button> ~~~ 组件被渲染时,`<slot></solt>`将会被替换为:Add todo: ~~~html <!-- 渲染后的 HTML --> <button class="btn-primary"> Add todo </button> ~~~ 插槽替换为字符仅仅是基本功能,它可以包含任何模板代码,如HTML代码: ~~~html <todo-button> <!-- 添加Font Awesome图标 --> <i class="fas fa-plus"></i> Add todo </todo-button> ~~~ 甚至其他组件: ~~~html <todo-button> <!-- 使用一个组件来加一个图标 --> <font-awesome-icon name="plus"></font-awesome-icon> Add todo </todo-button> ~~~ 如果组件模板没有任何插槽,提供任何内容都会被拒绝: ~~~html <!-- todo-button 组件模板--> <button class="btn-primary"> Create a new item </button> ~~~ ~~~html <todo-button> <!-- 下面文字不会被渲染 --> Add todo </todo-button> ~~~ ## 渲染域 如果你想在组件内使用`data`,例如: ~~~html <todo-button> Delete a {{ item.name }} </todo-button> ~~~ 插槽有权限访问有共同实例属性(如相同域)的其余模板。 ![](https://img.kancloud.cn/00/cf/00cf6bd787014eb22b2821d72b80212a_894x996.png) 插槽没有权限访问`<todo-button>`域,下例中访问`action`将不会工作: ~~~html <todo-button action="delete"> Clicking here will {{ action }} an item <!-- `action`将会提示未定义, 因为这个内容来自于`<todo-button>`, 不会定义在`<todo-button>` 组件内。 --> </todo-button> ~~~ 记住这条规则: >所有父模板都编译在父组件域,所有子模板都编译在子组件域。 ## 备用内容 有些情形给插槽一个备用内容(如默认值)是比较有用的,当没有任何内容提供给插槽时,将会使用备用内容渲染。例如下面的`<submit-button>`组件: ~~~html <button type="submit"> <slot></slot> </button> ~~~ 大部分时候我们都希望将button中的插槽渲染成`Submit`,将Submit设置为备用内容,我们可以将它放到`<slot></slot>`中间: ~~~html <button type="submit"> <slot>Submit</slot> </button> ~~~ 当使用这个组件且不为插槽提供内容时: ~~~html <submit-button></submit-button> ~~~ 会被渲染为: ~~~html <button type="submit"> Submit </button> ~~~ 但如果我们提供了内容: ~~~html <submit-button> Save </submit-button> ~~~ 渲染结果则为: ~~~ <button type="submit"> Save </button> ~~~ ## 具名插槽 使用多个插槽是经常用到的,如下面的`<base-layout>`组件模板: ~~~ <div class="container"> <header> <!-- 想把头内容放在这里 --> </header> <main> <!-- 想把主要内容放在这里 --> </main> <footer> <!-- 想把脚内容放到这里 --> </footer> </div> ~~~ 这种情形,插槽有一个特殊的属性`name`,它可以为每个插槽定义一个唯一的ID,这样你就可以决定哪些内容渲染到哪个地方了: ~~~html <div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div> ~~~ 没有指定`name`的插槽的名字是`default`。 模板中使用具名插槽,我们需要使用`v-slot`指令,`v-slot`参数就是插槽的名字: ~~~html <base-layout> <template v-slot:header> <h1> 这里可能是标题</h1> </template> <template v-slot:default> <p> 主要内容段落</p> <p>另一段内容</p> </template> <template v-slot:footer> <p>一些联系方式</p> </template> </base-layout> ~~~ 现在所有模板内的元素都被传递到相应的插槽里,最终渲染结果可能如下: ~~~html <div class="container"> <header> <h1>这里可能是标题</h1> </header> <main> <p> 主要内容段落</p> <p>另一段内容</p> </main> <footer> <p>一些联系方式</p> </footer> </div> ~~~ 注意:`v-slot`只能放在`template`内(有一种[例外](https://v3.vuejs.org/guide/component-slots.html#abbreviated-syntax-for-lone-default-slots)) ## 作用域插槽 有时,只有在子组件内部可以访问data内容是有用的。一个常用的例子就是使用组件渲染一个数组项,我们想定制数组项的渲染方式。 例如我们有一个组件,里面包含一个`todo-items`列表: ~~~ app.component('todo-list', { data() { return { items: ['Feed a cat', 'Buy milk'] } }, template: ` <ul> <li v-for="(item, index) in items"> {{ item }} </li> </ul> ` }) ~~~ 我们想把`{{ item }}`` 用一个插槽替换,以在父组件来定制: ~~~html <todo-list> <i class="fas fa-check"></i> <span class="green">{{ item }}</span> </todo-list> ~~~ 但这不会工作,因为只有`<todo-list>`组件才能够访问`item`和父组件提供的插槽内容。 要想让父组件提供的插槽内容能够使用`item`,我们可以添加`<slot>`标签并绑定成属性: ~~~html <ul> <li v-for="( item, index ) in items"> <slot :item="item"></slot> </li> </ul> ~~~ 一个插槽可以绑定多个属性: ~~~html <ul> <li v-for="( item, index ) in items"> <slot :item="item" :index="index" :another-attribute="anotherAttribute"></slot> </li> </ul> ~~~ 属性绑定到`<slot>`叫做`slot props`。现在在父组件域,我们可以使用`v-slot`绑定值来定义一个slot props名字: ~~~html <todo-list> <template v-slot:default="slotProps"> <i class="fas fa-check"></i> <span class="green">{{ slotProps.item }}</span> </template> </todo-list> ~~~ ![](https://img.kancloud.cn/c6/ef/c6ef14ba02eac288245c5c5009d966cc_1222x1230.png) 在这个例子中,我们选择命名一个包含全部slot props的对象`slotProps`,你可以使用任何名称随你喜欢。 ## 单默认插槽缩写语法 如上例,单个默认插槽,组件模板可以使用组件标签。这允许我们直接在组件上使用`v-slot`: ~~~html <todo-list v-slot:default="slotProps"> <i class="fas fa-check"></i> <span class="green">{{ slotProps.item }}</span> </todo-list> ~~~ 可以更简短,就像没有指定内容分配给默认插槽,无参数的`v-solt`指向默认插槽: ~~~html <todo-list v-slot="slotProps"> <i class="fas fa-check"></i> <span class="green">{{ slotProps.item }}</span> </todo-list> ~~~ 注意默认插槽的缩写语法不能混合具名插槽,这会导致作用域名不唯一: ~~~html <!--不合法, 会出现告警 --> <todo-list v-slot="slotProps"> <i class="fas fa-check"></i> <span class="green">{{ slotProps.item }}</span> <template v-slot:other="otherSlotProps"> slotProps is NOT available here </template> </todo-list> ~~~ 无论何时有多个插槽时,需要为所有插槽使用完整的模板语法: ~~~html <todo-list> <template v-slot:default="slotProps"> <i class="fas fa-check"></i> <span class="green">{{ slotProps.item }}</span> </template> <template v-slot:other="otherSlotProps"> ... </template> </todo-list> ~~~ ## 解构插槽props 内部原理是,作用域插槽是插槽内含有一个参数的函数实现: ~~~ function (slotProps) { // ... slot content ... } ~~~ 这意味着`v-slot`的值可以接受任何合法的JavaScript表达式,函数定义可以出现在参数位置。所以你可以使用[ES2005结构]()脱离特定的插槽props,像这样: ~~~html <todo-list v-slot="{ item }"> <i class="fas fa-check"></i> <span class="green">{{ item }}</span> </todo-list> ~~~ 这可以使用模板看起来更清爽,特别是插槽有多个props时。这也开启了其它可能性,比如重命名props,e.g. `item`转成`todo`: ~~~html <todo-list v-slot="{ item: todo }"> <i class="fas fa-check"></i> <span class="green">{{ todo }}</span> </todo-list> ~~~ 你甚至可以定义一个备用,在有些场景下插槽prop未定义时。 ~~~html <todo-list v-slot="{ item = 'Placeholder' }"> <i class="fas fa-check"></i> <span class="green">{{ item }}</span> </todo-list> ~~~ ## 动态具名插槽 动态指令参数同样也适用于`v-slot`,允许你使用动态插槽名: ~~~html <base-layout> <template v-slot:[dynamicSlotName]> ... </template> </base-layout> ~~~ ## 具名插槽简写 同`v-on`(@),`v-bind`(:)一样,`v-slot`也可以简写,用`#`号替换参数前(`v-slot:`)的所有内容。例如,`v-slot:header` 可以重写为`#header`: ~~~html <base-layout> <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> </base-layout> ~~~ 但是,和其他指令一样,缩写仅适用于提供了参数的情形。这意味着下面的例子是不合法的: ~~~html <!-- 这会触发一个警告 --> <todo-list #="{ item }"> <i class="fas fa-check"></i> <span class="green">{{ item }}</span> </todo-list> ~~~ 取而代之你必须为简写的插槽指定一个名字: ~~~html <todo-list #default="{ item }"> <i class="fas fa-check"></i> <span class="green">{{ item }}</span> </todo-list> ~~~