Vue - 2.插槽

816 阅读4分钟

匿名插槽

Vue 实现了一套内容分发的 API,将 slot 元素作为承载分发内容的出口,当组件渲染的时候,slot 将会被替换为子组件标签内的内容,插槽内可以包含任何模板代码,包括 HTML甚至其他组件,如果没有这个 slot 元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。

Vue.component('MyBtn', {
	template: `
        <button>
        	<slot></slot>
        </button>
    `,
})
const App = {
    template: `
        <div>
            <MyBtn>登录</MyBtn>
            <MyBtn>注册</MyBtn>
        </div>
    `,
}

后备内容

有时为一个插槽设置 默认的内容 是很有用的,它只会在没有提供内容的时候被渲染。

<button type="submit">
<slot>Submit</slot>
</button>

具名插槽

有时我们需要多个插槽,例如对于一个带有如下模板的 base-layout 组件:

<div class="container">
  <header>
    <!-- 我们希望把页头放这里 -->
  </header>
  <main>
    <!-- 我们希望把主要内容放这里 -->
  </main>
  <footer>
    <!-- 我们希望把页脚放这里 -->
  </footer>
</div>

这种情况下 slot 元素有一个特殊的属性:name,这个属性可以用来定义额外的插槽:

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

一个不带 nameslot 出口会带有隐含的名字 default

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

<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>

现在template 元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有 v-slottempate 中的内容都会被视为默认插槽的内容

如果希望更明确一些,仍然可以用 <template v-slot:default> 在一个 template 中包裹默认插槽的内容

渲染结果:

<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>

作用域插槽

父级模板里的所有内容都是在父级作用域中编译的,子模板里的所有内容都是在子作用域中编译的

有时让父组件插入的插槽中的内容能够访问子组件中才有的数据,需要用到作用域插槽

为了让 msg 在父级的插槽内容中可用,我们可以将 msg 作为 slot 元素的一个属性绑定上去

绑定在 slot 元素上的属性被称为插槽prop,现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽prop的名字

const msg = {
    data() {
        return {
            msg: '子组件中的数据'
        }
    },
    template: `
        <div>
            <slot :msg='msg'></slot>//1.在slot标签内,自定义属性绑定子组件中的的msg对象
            <h3>作用域插槽</h3>
        </div>
    `
}
const App = {
    components: {
        msg,
    },
    template: `
        <msg>
            <template v-slot:default='childMsg'>//2.使用带值的v-slot来定义提供的插槽prop的名字
                <h2>{{childMsg.msg}}</h2>//3.通过插槽名.子组件slot绑定的属性名来访问子组件中数据
            </template>
        </msg>
    `
}

插槽的缩写写法

独占默认插槽的缩写语法

当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot 直接用在组件上

<msg v-slot:default="childMsg">
  {{ childMsg.msg }}
</msg>

这种写法还可以更简单,不带参数的 v-slot 被假定对应默认插槽

<msg v-slot="childMsg">
  {{ childMsg.msg }}
</msg>

注意: 默认插槽的缩写语法不能*和具名插槽混用,因为它会导致作用域不明确,不要使用缩写

只要出现多个插槽,请始终为所有的插槽使用完整的基于 template 的语法:

<msg>
  <template v-slot:default="childMsg">
    {{ childMsg.msg }}
  </template>

  <template v-slot:other="otherSlotProps">
    ...
  </template>
</msg>

具名插槽的缩写

把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header

<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>

和其它指令一样,该缩写只在其有参数的时候才可用,如果你希望使用缩写的话,你必须始终明确插槽名

<msg #default="childMsg">
  {{ childMsg.msg }}
</msg>