结合element-ui源码,一次性理解vue插槽slot

718 阅读5分钟

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

一、插槽的应用场景

props:单向数据流,常用于父组件给子组件传递值,属于数据层面

slot:我们引入了一个组件,但是想要差异化配置组件,并且向组件中添加自定义的内容。属于dom层面。

二、插槽的初步使用

【注】: 这里的插槽语法都是2.6.0以后的使用方法,而后面的源码使用的是比较低的版本的写法。

为表示区分,声明为child组件的样式都为蓝绿色

场景一:主页面A需要引入一个组件child

//A
<template>
  <div>
    <h3>主页面</h3>
    <child> </child>
  </div>
</template>
<script>
import child from "./child";
export default {
  components: {
    child,
  },
};
</script>
<style>
</style>
//组件child
<template>
  <div>
    <div class="font">子组件</div>
  </div>
</template>
<script>
export default {};
</script>
<style>
.font {
  color: #05bbc9;
  font-weight: 900;
}
</style>

达到的效果是主页面A可以使用组件B

image.png

场景二:主页面A需要在子组件中写入主页面的内容

//A
<template>
  <div>
    <h3>主页面</h3>
    主页面自己的数据
    <hr />
    <child> 主页面写入组件的数据 </child>
  </div>
</template>
<script>
import child from "./child";
export default {
  components: {
    child,
  },
};
</script>
<style>
</style>
//组件child
<template>
  <div>
    <h4 class="font">子组件</h4>
    <div class="font">子组件自己的数据</div>
    <slot class="font"></slot>
  </div>
</template>
<script>
export default {};
</script>
<style>
.font {
  color: #05bbc9;
  font-weight: 900;
}
</style>

image.png

理解

  • 主页面在引用组件时传入了一段文本
  • 组件声明了一个<slot></slot>插槽接收主页面传递的内容

:你可能会注意到,这里主页面写入组件的数据虽然引用了组件页面的样式,但是没有起作用。如果想要加字体样式,需要在主页面中声明child组件的样式为class="font"。

三、插槽的分类

1.后备内容(默认插槽)

场景:组件具有默认值,如果在使用时,主页面不传入数据,则使用默认值;否则使用主页面传入的值

方法:在声明插槽的标签中写入默认值即可

 <slot class="font">如果主页面不传入数据,显示我</slot>

2.具名插槽

场景:组件中有多个插槽,主页面传入的数据需要渲染在不同的插槽里面

vue官方文档说明如下:

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

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

方法:

1.在组件中的插槽域中声明name属性

2.在主页面引用时,使用<template v-slot:name></template>

<template>
  <div>
    <h3>主页面</h3>
    主页面自己的数据
    <hr />
    <child>
      <template v-slot:third>
        <h3 class="font">third</h3>
      </template>
      <template v-slot:second>
        <h3 class="font">second</h3>
      </template>
      <template v-slot:first>
        <h3 class="font">first</h3>
      </template>
      <template>
        <h3 class="font">default</h3>
      </template>
    </child>
  </div>
</template>
<template>
  <div>
    <h4 class="font">组件</h4>
    <div class="font">组件自己的数据</div>
    <hr />
    <div class="font">主页面传来的数据</div>
    <div style="color: red">
      <p>first 的数据</p>
      <slot name="first"></slot>
    </div>
    <div style="color: red">
      <p>second 的数据</p>
      <slot name="second"></slot>
    </div>
    <div style="color: red">
      <p>third 的数据</p>
      <slot name="third"></slot>
    </div>
    <div style="color: red">
      <p>default 的数据</p>
      <slot></slot>
    </div>
  </div>
</template>

image.png

细节

  • 主页面未指定v-slot属性的插槽自动渲染到没有name属性的slot标签中
  • 主页面声明的顺序不影响组件中slot中元素排列的顺序
  • v-slot可以简写为“#”,如同v-bind可以简写为“:”

3.作用域插槽

场景:解决主页面访问子组件的数据问题

子组件定义一个user对象,主页面希望访问此数据:

<template>
  <div>
    <h4 class="font">组件</h4>
    <div class="font">组件自己的数据</div>
    <hr />
    <div class="font">主页面传来的数据</div>
    <slot></slot>
  </div>
</template>
<script>
export default {
  data() {
    return {
      user: {
        name: "zs",
        password: "root",
      },
    };
  },
};
</script>

运行报错:

image.png

这是因为子组件中的数据是在子组件中编译的,而主页面中的数据是在主页面中编译的。

解决方法:

在子组件的slot标签上绑定user对象

 <slot :user="user"></slot>

主页面中使用该对象

    <child>
      <template v-slot:default="slotProps">
        {{ slotProps.user.name }}
        {{ slotProps.user.password }}
      </template>
    </child>

如果页面中只存在单个插槽,可以简写为:

    <child>
      <template v-slot="slotProps">
        {{ slotProps.user.name }}
        {{ slotProps.user.password }}
      </template>
    </child>

多个插槽的情况:

<template>
  <div>
    <h3>主页面</h3>
    主页面自己的数据
    <hr />
    <child>
      <template v-slot="slotProps">
        {{ slotProps.job.rank }}
        {{ slotProps.job.name }}
      </template>
      <template v-slot:first="slotProps">
        {{ slotProps.user.name }}
        {{ slotProps.user.password }}
      </template>
      <template v-slot:second="slotProps">
        {{ slotProps.address.provice }}
        {{ slotProps.address.city }}
      </template>
    </child>
  </div>
</template>
<template>
  <div>
    <h4 class="font">组件</h4>
    <div class="font">组件自己的数据</div>
    <hr />
    <div class="font">主页面传来的数据</div>
    <div style="color: red">
      <p>first 的数据</p>
      <slot name="first" :user="user"></slot>
    </div>
    <div style="color: red">
      <p>second 的数据</p>
      <slot name="second" :address="address"></slot>
    </div>
    <div style="color: red">
      <p>default 的数据</p>
      <slot :job="job"></slot>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      user: {
        name: "zs",
        password: "root",
      },
      address: {
        provice: "sx",
        city: "xa",
      },
      job: {
        rank: "1",
        name: "java",
      },
    };
  },
};
</script>

image.png

vue官方文档提到上述报错的原因:

在这个例子中,我们选择将包含所有插槽 prop 的对象命名为 slotProps,但你也可以使用任意你喜欢的名字。

此外,为了使用方便,我们可以解构slotProps对象:

<template>
  <div>
    <h3>主页面</h3>
    主页面自己的数据
    <hr />
    <child>
      <template v-slot="{ job }">
        {{ job.rank }}
        {{ job.name }}
      </template>
      <template v-slot:first="{ user }">
        {{ user.name }}
        {{ user.password }}
      </template>
      <template v-slot:second="{address}">
        {{ address.provice }}
        {{ address.city }}
      </template>
    </child>
  </div>
</template>

当然,也可以进一步缩写,把v-slot换成“#”,在此不做赘述。

四、element-ui 中对slot插槽的使用

引入两个知识:

1.slot历史版本的问题

    <template v-slot:first="{ user }">
        {{ user.name }}
        {{ user.password }}
      </template>

      <template slot="first" slot-scope="slotProps">
        {{ slotProps.user.name }}
        {{ slotProps.user.password }}
      </template>

是等价的

2、vm.$slots:用来访问被插槽分发的内容。常用于JSX渲染函数

 <template v-slot:testSlot> mainSlot </template>
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
  <div style="color: red">
      <p @click="printScope($slots.testSlot)">$slot 的数据</p>
      <slot name="testSlot"> </slot>
   </div>
 

image.png

3.阅读element-ui文档:

element-ui官方文档示例如下:


<div> 
    <el-input placeholder="请输入内容" v-model="input1"> 
        <template slot="prepend">Http://</template>
        
        /* 等价于*/
        /*  <template v-slot="prepend">Http://</template>  */
    </el-input> 
</div>

如果我们配置了上述代码,那么上面template这行代码会被分发到源码中的<slot name="prefix"></slot>位置,从而生成input的前缀。

(源码位置:element-ui/packages/input/src/input.vue)

image.png

五、总结

我们在使用组件库的时候,经常会接触到slot插槽的相关属性,每次都让人感觉到雾里看花。探究其本源以及查看源码中的使用方法似乎成了解密的唯一途径。

props和slot都是为了封装组件而生的,但是他们存在一些不同之处:

相同点:

  • 都是发生在父子组件之间的关系;
  • 都是为了应对父组件调用子组件的场合;

不同点

区别一:设计思想

props的设计思想是传递状态,将数据驱动组件的思想贯彻到底,子组件的渲染取决于父组件传递的数据;

slot的设计思想是传递DOM节点,将父组件的模板代码节点直接传递给子组件的某个slot,来达到最终渲染的目的;

区别二:作用范围

父组件在调用子组件的时候申明并赋值变量,那么子组件内将其加到props列表后,就可以接收并使用,但是一旦传递过来,作用域就发生变化;

子组件虽然为父组件预留slot,但是slot的作用域依然属于父组件,所以可以访问到父组件内的所有状态。

致谢:

caibaojian.com/vue-slot.ht…

copyfuture.com/blogs-detai…

v2.cn.vuejs.org/