Vue插槽的理论与实践

94 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第24天,点击查看活动详情

什么是插槽?

插槽就是封装组件的一个占位符,在某些场景中,我们可能想要为子组件传递一些模板片段,让子组件在它们的组件中渲染这些片段。

举个栗子:

my-button组件

<my-button>
    <!-- 插槽内容 -->
    Click me
</my-button>

my-button组件模板

<button>
    <!-- 插槽出口 -->
    <slot></slot>
</button>

最终渲染在页面的结果

<button>
  Click me!
</button>

slot标签是一个插槽出口,父组件提供的插槽内容会把 slot 标签替换掉。插槽内容可以是任何模板代码,比如HTML、甚至其他组件。

没有 slot标签时:

my-button组件

<my-button>
    <!-- 插槽内容 -->
    Click me
</my-button>

如果my-button组件模板中没有 slot标签,则该组件起始标签和结束标签之间的任何内容都会被抛弃。最终渲染结果同下:

<button>
</button>

后备内容

给插槽设置一个后备内容很有必要,当组件没有提供插槽内容时,插槽会渲染后备内容。

<button>
    <!-- 设置后备内容 -->
    <slot>Submit</slot>
</button>

my-button组件

<my-button>
    <!-- 没有插槽内容 -->
</my-button>

最终渲染结果

<button>
    Submit
</button>

但如果提供了插槽内容,my-button组件

<my-button>
    <!-- 插槽内容 -->
    Click me
</my-button>

则提供的插槽内容会替代后备内容:

<button>
  Click me
</button>

具名插槽

如果一个组件需要预留多个插槽,,<slot> 元素有一个特殊的 attribute:name。这个 attribute 可以用来定义具名插槽:

<div class="container">
    <header>
        <!-- 页头 -->
        <slot name="header"></slot>
    </header>
    <main>
        <!-- 内容 -->
        <slot></slot>
    </main>
    <footer>
        <!-- 页脚 -->
        <slot name="footer"></slot>
    </footer>
</div>

不带 name<slot> 标签会带有隐含的名字“default”。

在向具名插槽提供内容的时候,在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:

<my-home>
    <template v-slot:header>
        <h1>header</h1>
    </template>
​
    <p>default</p>
    <p>main</p>
​
    <template v-slot:footer>
        <p>footer</p>
    </template>
</my-home>

现在 <template> 元素中的所有内容都将会被传入相应的插槽。没有被<template> 包裹的内容都会被视为默认插槽的内容。

如果希望语义更明确,也可以在 <template> 中包裹默认插槽的内容:

<my-home>
    <template v-slot:header>
        <h1>header</h1>
    </template>
​
    <template v-slot:default>
        <p>default</p>
        <p>main</p>
    </template>
​
    <template v-slot:footer>
        <p>footer</p>
    </template>
</my-home>

具名插槽的缩写

v-onv-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header

<my-home>
    <template #header>
        <h1>header</h1>
    </template>
​
    <template #default>
        <p>default</p>
        <p>main</p>
    </template>
​
    <template #footer>
        <p>footer</p>
    </template>
</my-home>

作用域插槽

对于插槽的编译作用域,记住这样一条规则:父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

普通插槽是无法访问子组件中的数据的,当在插槽中想使用子组件的数据时,就要用到作用域插槽。

举个栗子:

现在有一个表格组件,表格的列名是通过子组件提供的,绑定在 <slot> 元素上的 attribute col 被称为插槽 prop

<thead>
    <slot name="thead" v-for="item in cols" :col="item"></slot>
</thead>
​
cols数据
cols: ["序号", "书名", "单价", "日期", "作者"]

在插槽内容中接收并使用作用域插槽,使用col来定义子组件提供的插槽 prop 名字:

<my-table>
    <template #thead=“col”>
        <tr>
            <th>{{col}}</th>
        </tr>
    </template>
</my-table>