10

我们知道在HTML中标签可以分为闭合标签空标签,其中大多数都是闭合标签,只有少数空标签,比如:<input/>`<img/><base/><hr/>等。在Vue中定义的组件也可以采用两种方式来书写。

在一个组件的<template>中假如显示一个标题<h1>Hello World!</h1>,当我们在调用时,不管是采用<组件名/>或者<组件名></组件名>都会显示 “hello world!“,由于在闭合标签中可以包含子节点和文本,因此我们可以这样来使用<组件名>我是内容</组件名>,但是运行的结果,确什么也没有改变。

如果想要在组件传入的内容正确投射,就需要一套机制来处理,在Vue中自然就是slot(插槽)机制,在React中可以通过this.props.children来获取,而在Angular中可以通过<ng-content></ng-content>来放置转入的内容。

React 的处理方式

在React中传入this.props.children的值有三种可能:

  1. 没有子节点,结果为 undefined
  2. 子节点,结果为 object
  3. 多个子节点,结果为 array

如果要对传入的数据进行处理,React还提供了一个工具方法React.Children来处理this.props.children。总的来说React把一切处理完全交给开发人员来,完全透明。

React的这种处理方式相对来说也更加灵活,对于Vue的作用域插槽机制也能很简单的实现。

Angular 的处理方式

相对于React的完全透明模式,Angular则在传入内容时通过属性类(class)标签三种模式来区分和限定内容的作用域。在组件内容使用<ng-content select="xx"></ng-content>来作为占位,具休使用方式如下:

  1. 传属性及属性值
<div card-body>属性</div>
<ng-content select="[card-body]"></ng-content>

<div card-type="body">属性值</div>
<ng-content select="[card-type=body]"></ng-content>

<div card body>多个属性值组合</div>
<ng-content select="[card][body]"></ng-content>
  1. 传类(class)
<div class=".card-body">类</div>
<ng-content select=".card-body"></ng-content>

<div class="card body">多个类组合</div>
<ng-content select=".card.body"></ng-content>
  1. 传标签
<card-body></card-body>
<ng-content select="card-body"></ng-content>
  1. 多个插槽
<ng-content select="header"></ng-content>
<div class="body">balabala...</div>
<ng-content select="footer"></ng-content>

从上面的使用方式来看Angular的插槽功能还是很强大的。

Vue 的处理方式

回归到本文的主题,在Vue 2.6.0过后引入了新语法机制将以前版本的slotscope-slot使用一个属性来表示。具体缘由可查看其RFC

在新的语法中v-slot只允许使用在 组件<template></template> 标签中,并且在只能<template>套组件和子<template>

  1. 默认插槽

在组件中直接放一个<slot></slot>标签就可以接收来自组件中的内容,如果<slot>不为空,那么内容将作为默认值显示。

在调用时就可以使用v-slot:default在组件上或者其子节点<template>上。

  1. 具名插槽及缩写

具名插槽就是把分布在页面中不同位置的<slot>分配一个名字来标识,以区分其功能。在使用时只需要给<slot>添加一个name属性即可,例如:<slot name="footer"></slot>。在使用时把默认插槽的default换成相应的名字即可,即:<template v-slot:footer></template>

在使用过程没有必要每次都重复写v-slot:,同v-onv-bind一样,v-slot也有其缩写形式,即把参数之前的所有内容 (v-slot:) 替换为字符 #,即<template #footer></footer>

  1. 作用域插槽

作用域插槽的机制就是被调用的组件把组件内部的状态通过属性暴露给当前上下文。简单点说就是它只提供数据,至于当前数据怎么展示它不关心,类似于React的Render Props机制。

由于Vue官方文档写得很清楚明白这里直接上知识点:

  • 解构插槽Prop
<current-user v-slot="{ user }">
  {{ user.firstName }}
</current-user>
  • 解构时提供别名和提供初始值
<current-user v-slot="{ user: person }">
  {{ person.firstName }}
</current-user>

<current-user v-slot="{ user = { firstName: 'Guest' } }">
  {{ user.firstName }}
</current-user>

官方提到在只有默认插槽时可以使用插槽的缩写语法,将v-slot:default="slotProps"写成v-slot="slotProps",但是在使用时官方也说了不能和具名插槽混用,因为它会导致作用域不明确。

下面通过一个示例来实现React中的Render Props机制。

<template>
  <div style="height:100%" @mousemove="handleMouseMove">
    <p>The current mouse position is {{ x }}, {{ y }}</p>
    <slot :position="position"/>
  </div>
</template>

<script>
  export default {
    name: 'Mouse',
    data() {
      return {
        x: 0,
        y: 0
      }
    },
    computed: {
      position: function() {
        return {
          x: this.x,
          y: this.y
        }
      }
    },
    methods: {
      handleMouseMove(event) {
        this.x = event.clientX
        this.y = event.clientY
      }
    }
  }
</script>

然后在其它组件中调用

<mouse>
  <template v-slot:default="{position}"> 
    <img src="http://iph.href.lu/64x64?text=图片跟随鼠标" :style="{position: 'absolute', left: position.x + 'px', top: position.y + 'px'}"/>
  </template>
</mouse>
  1. 动态插槽

动态插槽欢迎通过编程动态控制当前组件的插槽名,使用方式如下:

<template v-slot:[dynamicSlotName]>...</template>
  1. 多层插槽的嵌套
<foo v-slot="foo">
  <bar v-slot="bar">
    <baz v-slot="baz">
      {{ foo }} {{ bar }} {{ baz }}
    </baz>
  </bar>
</foo>

总结

本文只是简单对Vue的Slot的知识点做一个简单的笔记,更多的知识点官方文档比较靠谱,文中并没有提到在JSX语法下的插槽使用方式,如果想要了解请查看我的另外一篇文章Vue中jsx不完全应用指南关于插槽部分的内容。


jenemy
1.7k 声望744 粉丝

从事前端多年,技术依然很渣的IT程序员。