一次性讲明白vue插槽slot

vue插槽slot

1、前言

vue官方文档中在"组件基础"内容中提到组件能够经过插槽分发内容,那插槽是怎么使用的呢?它要解决什么场景的问题呢?javascript

咱们在构建页面过程当中通常会把用的比较多的公共的部分抽取出来做为一个单独的组件,可是在实际使用这个组件的时候却又不能彻底的知足需求,我但愿在这个组件中添加一点东西,这时候咱们就须要用到插槽来分发内容。vue

注意:如下的全部内容是基于vue版本 2.6.0 起java

2、插槽是什么

下面看一个例子bash

写一个父组件: test.vueapp

<template>
    <div>
      <div>你们好我是父组件</div>
      <myslot>
        <p>测试一下吧内容写在这里了可否显示</p>
      </myslot>
    </div>
</template>

<script>
  import myslot from './myslot';
  export default {
    components: {
      myslot
    }
  }
</script>

<style>
</style>

复制代码

写一个子组件:myslot.vue测试

<template>
  <div>
      <div>我是子组件</div>
  </div>
</template>

<script>
</script>

<style>
</style>
复制代码

运行代码,发现,最终渲染的效果是ui

你们好我是父组件
我是子组件
复制代码

那若是我想实现显示父组件中p标签的内容怎么办spa

修改子组件:myslot.vuecode

<template>
  <div>
      <div>我是子组件</div>
      <p>如今测试一下slot</p>
      <slot></slot>
  </div>
</template>

<script>
</script>

<style>
</style>

复制代码

运行代码,能够看到如下效果component

你们好我是父组件
我是子组件
如今测试一下slot
测试一下吧内容写在这里了可否显示
复制代码

官方文档对于插槽的应用场景是这样描述的:

咱们常常须要向一个组件传递内容

Vue 自定义的 <slot> 元素让这变得很是简单

只要在须要的地方加入插槽就好了——就这么简单!

结合上面的例子来理解就是这样的:

1.父组件在引用子组件时但愿向子组价传递模板内容<p>测试一下吧内容写在这里了可否显示</p>

2.子组件让父组件传过来的模板内容在所在的位置显示

3.子组件中的<slot>就是一个槽,能够接收父组件传过来的模板内容,<slot> 元素自身将被替换

4.<myslot></myslot>组件没有包含一个 <slot> 元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃

2、插槽的做用

让用户能够拓展组件,去更好地复用组件和对其作定制化处理

3、插槽的分类

1.默认插槽

在一个 <submit-button> 组件中:

<button type="submit">
  <slot></slot>
</button>
复制代码

咱们可能但愿这个 <button> 内绝大多数状况下都渲染文本“Submit”,可是有时候却但愿渲染文本为别的东西,那怎么实现呢?

咱们能够将“Submit”做为后备内容,咱们能够将它放在 <slot> 标签内:

<button type="submit">
  <slot>Submit</slot>
</button>
复制代码

如今当我在一个父级组件中使用 <submit-button> 而且不提供任何插槽内容时:

<submit-button></submit-button> 复制代码

后备内容“Submit”将会被渲染:

<button type="submit">
  Submit
</button>
复制代码

可是若是咱们提供内容:

<submit-button>
  Save
</submit-button>
复制代码

则这个提供的内容将会被渲染从而取代后备内容:

<button type="submit">
  Save
</button>
复制代码

2.具名插槽

有时咱们写了一个子组件,咱们但愿

<template>
    <div class="container">
      <header>
        <!-- 咱们但愿把页头放这里 -->
      </header>
      <main>
        <!-- 咱们但愿把主要内容放这里 -->
      </main>
      <footer>
        <!-- 咱们但愿把页脚放这里 -->
      </footer>
    </div>
</template>
复制代码

对于这样的状况,<slot> 元素有一个特殊的 attribute:name。这个 attribute 能够用来定义额外的插槽:

<template>
  <div class="container">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>
复制代码

一个不带 name<slot> 出口会带有隐含的名字“default”。

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

<template>
  <myslot>
    <div>你们好我是父组件</div>
    <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 footer info</p>
    </template>
  </myslot>
</template>

<script>
  import myslot from './myslot';
  export default {
    components: {
      myslot
    }
  }
</script>
<style>
</style>
复制代码

最终的渲染结果能够看到

Here might be a page title
你们好我是父组件
A paragraph for the main content.

And another one.

Here's footer info 复制代码

父组件中会向子组件中具名传递对应的模板内容,而没有指定名字的模板内容会传递给子组件中不带 name<slot>

固然,若是父组件中

<template v-slot:default>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
</template>
复制代码

一样是传递给子组件中不带 name<slot>

注意:

v-slot 只能添加在 <template>

具名插槽在书写的时候可使用缩写,v-slot用#来代替

<template>
  <myslot>
    <div>你们好我是父组件</div>
    <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 footer info</p>
    </template>
  </myslot>
</template>

<script>
  import myslot from './myslot';
  export default {
    components: {
      myslot
    }
  }
</script>

<style>
</style>
复制代码

3.做用域插槽

这里主要解决的是父组件在向子组件插槽传递模板内容时存在访问子组件数据的问题

还记得默认插槽吗?若是子组件中写在 <slot> 标签内后备内容是与 该组件的data属性双向数据绑定的

<template>
  <div>
    <span>
      <slot>{{user.firstName}}</slot>
    </span>
  </div>
</template>

<script>
  export default {
    data () {
      return {
        user:{
          firstName:'gerace',
          lastName:'haLi'
        }
      }
    }
  }
</script>
<style>
</style>

复制代码

父组件在引用子组件时,但愿可以换掉备用内容

<template>
  <myslot>{{ user.firstName }}</myslot>
</template>

<script>
  import myslot from './myslot';
  export default {
    components: {
      myslot
    }
  }
</script>
<style>
</style>
复制代码

运行代码这时你会发现提示报错

Property or method "user" is not defined on the instance but referenced during render.
TypeError: Cannot read property 'firstName' of undefined
复制代码

这里为何?vue官方文档给出了答案

父级模板里的全部内容都是在父级做用域中编译的;子模板里的全部内容都是在子做用域中编译的

那应该怎么解决这个问题?

为了让 user 在父级的插槽内容中可用,咱们能够将 user 做为 <slot> 元素的一个 attribute 绑定上去:

<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>
复制代码

绑定在 <slot> 元素上的 attribute 被称为插槽 prop。如今在父级做用域中,咱们可使用带值的 v-slot 来定义咱们提供的插槽 prop 的名字:

<template>
  <myslot>
      <template v-slot:default="slotProps">
      {{ slotProps.user.firstName }}
      </template>
  </myslot>
</template>

<script>
  import myslot from './myslot';
  export default {
    components: {
      myslot
    }
</script>
<style>
</style>

复制代码

上面例子,咱们选择将包含全部插槽 prop 的对象命名为 slotProps,但你也可使用任意你喜欢的名字。

针对上面只给默认插槽传递模板内容的例子,在写法上能够采用默认插槽的缩写语法

<template>
  <myslot v-slot:default="slotProps">
     {{ slotProps.user.firstName }}
  </myslot>
</template>

<script>
  import myslot from './myslot';
  export default {
    components: {
      myslot
    }
</script>
<style>
</style>
复制代码

上面代码也能够直接改成

<template>
  <myslot v-slot="slotProps">
     {{ slotProps.user.firstName }}
  </myslot>
</template>
复制代码

注意:

默认插槽的缩写语法不能和具名插槽混用,由于它会致使做用域不明确:

<template>
  <myslot v-slot="slotProps">
     {{ slotProps.user.firstName }}
     <template v-slot:other="otherSlotProps">
   		slotProps is NOT available here
     </template>
  </myslot>
</template>
复制代码

下面再看一下多个插槽的状况

子组件

<template>
  <div>
    <span>
      <slot v-bind:userData="user" name="header">
        {{ user.msg }}
      </slot>
      <slot v-bind:hobbyData="hobby" name="footer">
        {{ hobby.fruit }}
      </slot>
    </span>
  </div>
</template>

<script>
  export default {
    data () {
      return {
        user:{
          firstName: 'gerace',
          lastName: 'haLi',
        },
        hobby:{
          fruit: "apple",
          color: "blue"
        }
      }
    }
  }
</script>
<style>
</style>
复制代码

父组件

<template>
  <myslot>
      <template v-slot:header="slotProps">
        {{ slotProps.userData.firstName }}
      </template>
      <template v-slot:footer="slotProps">
        {{ slotProps.hobbyData.fruit }}
      </template>
  </myslot>
</template>

<script>
  import myslot from './myslot';
  export default {
    components: {
      myslot
    }
</script>
<style>
</style>

复制代码

针对多个插槽的状况,在写法上能够解构插槽prop,父组件的写法以下

<template>
  <myslot>
      <template v-slot:header="{userData}">
        {{ userData.firstName }}
      </template>
      <template v-slot:footer="{hobbyData}">
        {{ hobbyData.fruit }}
      </template>
  </myslot>
</template>

<script>
  import myslot from './myslot';
  export default {
    components: {
      myslot
    }
  }
</script>
<style>
</style>
复制代码

在具名插槽的介绍部分有讲过,具名插槽可使用缩写,v-slot可使用#来代替,因此以上代码能够写成:

<template>
  <myslot>
      <template #header="{userData}">
        {{ userData.firstName }}
      </template>
      <template #footer="{hobbyData}">
        {{ hobbyData.fruit }}
      </template>
  </myslot>
</template>

<script>
  import myslot from './myslot';
  export default {
    components: {
      myslot
    }
  }
</script>
<style>
</style>
复制代码

可是须要注意的是该缩写只在其有参数的时候才可用。这意味着如下语法是无效的:

<!-- 这样会触发警告 -->
<template> <myslot> <template #="{userData}"> {{ userData.firstName }} </template> <template #="{hobbyData}"> {{ hobbyData.fruit }} </template> </myslot> </template>
复制代码