1

组件的Vue作用域的隔离

<current-user>
  {{ user.firstName }}
</current-user>
<!-- current-user这一组件的内部实现 -->
<span>
  <slot>
    {{ user.lastName }}
  </slot>
</span>

这两段代码,就已经有了两个不同的Vue作用域,一个是 使用current-user模块 的页面或模块所具有的Vue实例与对应的作用域,另一个则是current-user这一模块自身对应的Vue实例与对应作用域。

这两个作用域彼此互相隔离,再不加任何额外设置的情况下,无法访问到彼此Vue实例,因此不要说什么datamethods之类的,Vue实例所具有的成员统统都是无法交互的。

正是在此基础上,当我们需要达成这两个作用域能彼此知晓一些信息的目标时,不得不采取一些设置。这设置里就包括了作用域插槽。同时,这两个作用域可以认为存在父子关系,即第一段代码中使用current-user模块的页面或模块(比如说,第一段代码是一个index.html的节选。)的Vue作用域是第二段代码的被调用的current-user模块(比如说,这段代码是一个currentuser.vue文件的节选)的Vue作用域的父作用域(index.html引入了currentuser.vue从而使用<curent-user></curent-user>。在这篇文章里,称index.html的Vue实例是currentuser.vue的Vue实例的父亲)。基于此,当我们想要让两个不能轻易交互但有使用关系的Vue实例交互彼此的信息,那么有两种情况:第一,父知晓子的信息;第二,子知晓父的信息。

作用域插槽的真正目的是让父知晓子的信息

一开始误以为作用域插槽的目的是让子能知晓父的信息,让我始终无法理解作用域插槽。但如果带着作用域插槽的目的是让父知子这一想法去看,就觉得官方文档豁然开朗了。官方文档传送门

截图1

图中的user从何而来?这么来的

current-user.vue
<template>
<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>
</template>

<script>
export default {
    data(){
        return {
            user:{
                firstName:"张",
                lastName:"飞",
            }
        }
    }
}
</script>

<style></style>

是的,这个user打一开始就是在current-user这个模块(current-user.vue这个文件)内部的Vue作用域(想想我们之前说的父和子,也就是说user数据在子模块中)中,官方文档这一章节想要解决的不是模块内部的{{user.lastName}}user需要父传入,而是父中{{ user.firstName }}user需要子告知父!

映射关系图

经过解析这幅图上的映射关系,我们就能明白父为何能够知晓子中的信息。

首先,父在使用组件时,不能像原来一样舒舒服服地直接写上想要替换掉<slot>的内容,而是要先用一个<template>

其次,父亲必须要知道想要了解的子的名字,如果只有一个孩子,那这个孩子肯定叫'defualt',此时在子中不写明无所谓,在父中不写明也无所谓。即图中的name="default"v-slot:default="slotProps":defualt均可删去。

然后,父亲想要知道子中的数据,子必需孝顺(开发者必需事先考虑到)而准备好一个slot标签并带上v-bind:[key]="dataOfMine"属性。

现在,如前述所言,孝顺的子考虑到父亲需要知道自己的数据而做好了准备——而在自己的模块中写一个<slot></slot>并为它带上v-bind:[key]="dataOfMine"属性。对每一个带v-bind:[key]="dataOfMine"属性的<slot>元素。根据父亲的<template></template>中的属性v-slot:childname="propertiesSentByChild",子告知父的信息(通过v-bind:[key]="dataOfMine"属性的<slot>元素告知的,至于怎么回事儿你继续往下看。)会被装入一个名为propertiesSentByChild的父Vue实例作用域内可访问的对象(或者干脆理解成Map好了,毕竟最典型的行为是通过键得到值。),这个对象通过propertiesSentByChild来引用,通过propertiesSentByChild。key(对没错,这里的key就是上面几行那个v-bind:[key]="dataOfMine"里的key)得到dataOfMine(对没错,这里的key就是上面几行那个v-bind:[key]="dataOfMine"里的dataOfMine),而这个dataOfMine就是子Vue实例中的数据。比如这里把dataOfMine写成user的话,那么那个firstname为张,lastname为飞的对象就在父中被propertiesSentByChildkey.key得到啦,如果你console.log(propertiesSentByChild.key.firstName),你还能得到“张”的输出呢。另外这里的propertiesSentByChild也是任由开发者自己写的,只要前后对应一致即可。

到此,作用域插槽的真正目的和写法已经讲明白了。接下来让我们看看它的实际使用。

作用域插槽的实际使用

ElementUI的table组件的”自定义列模板“样式中操作列的按钮使用了作用域插槽

这里就只贴有关部分代码了

    <el-table-column label="操作">
      <template slot-scope="scope">
        <el-button
          size="mini"
          @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
        <el-button
          size="mini"
          type="danger"
          @click="handleDelete(scope.$index, scope.row)">删除</el-button>
      </template>
    </el-table-column>

这里使用的时已经废弃的写法,贴一下废弃写法转换为目前推荐写法的对应关系
废弃写法和新写法的对应关系
还是老样子,如果只有一个<slot>则 default 写不写无所谓。

那么在这个ElementUI的实例中,可以看到它对作用域插槽的使用:scope.$index,这说明$index<el-table-column>模块内部的一个数据,经过console.log()大法测试,发现这个对应的就是被点击删除的按钮所在行的行数减一。

这样一想还挺合理的,<el-table-column>中的数据遍历<el-table>:data=绑定的数组而不断生成,每次生成,其内部(也就是子)都存在一个序号$index来记录自己是第几个,此时我们想在父中访问这个数据,就要用到作用域插槽了。


阳光号
129 声望5 粉丝