聊聊Vue.slot原理,起探究下slot 是怎么实现的!

聊聊Vue.slot原理,起探究下slot 是怎么实现的!

浏览次数:913次
信息来源: 银河系资源网
更新日期: 2022-05-13
文章简介

本篇文章给大家分享一下Vue干货,聊聊你可能不知道的Vue.slot原理,希望对大家有所帮助!相信不管是日常业务开发,还是封装基础组件,插槽slot 都是经常出现在我们的视野的,因为它为我们编程实现提供了很多便捷。可...

本篇文章给大家分享一下Vue干货,聊聊你可能不知道的Vue.slot原理,希望对大家有所帮助!

相信不管是日常业务开发,还是封装基础组件,插槽slot 都是经常出现在我们的视野的,因为它为我们编程实现提供了很多便捷。可能大家对于 slot 的用法已经烂透于心了,不管是 具名插槽 ,还是 作用域插槽 各种用法等等...那大?们又知不知道 slotslot-scope 底层是怎么实现的呢?

通俗易懂10分钟就能带走Vue.slot干货源码实现分析!!!跟着笔者一起探究下 Vue(v2.6.14) 中的插槽 slot 是怎么实现的!!本文主要会分两块进行讲解:

  • 普通插槽(具名插槽、默认插槽)

  • 作用域插槽

这篇文章没有晦涩的源码解析,直接用大白话讲解,所以不管大家对Vue源码的熟悉程度,都是能看明白的。通过现场调试,让你看清 Vueslot 是如何实现的。let's go go go!(学习分享:vue教程)

一、回顾 slot 用法

先跟大家一起回顾下插槽的大概用法。这里的 slot 用法使用 2.6.0 的新标准(本文也会带一下 v2.5 的写法的跟 v2.6 在源码实现上有什么区别!)。

如果想详细了解用法可以去官网详细看看Vue 的 slot 文档

https://cn.vuejs.org/v2/guide/components-slots.html

1. 默认插槽

<!-- 子组件 -->
<template>
  <div class="wrapper">
    <!-- 默认插槽 -->
    <div class="main">
      <slot></slot>
    </div>
</template>

<!-- 父组件 -->
<my-slot>
  <template>
    <h1>默认插槽</h1>
  </template>
</my-slot>

页面展示效果如图:

2. 具名插槽

接着上述的案例,添加具名插槽 header ,代码如下:

<!-- 子组件 -->
<template>
  <div class="wrapper">
    <!-- header 具名插槽 -->
    <header class="header">
      <slot name="header"></slot>
    </header>
    <!-- 默认插槽 -->
    <div class="main">
      <slot></slot>
    </div>
</template>

<!-- 父组件 -->
<my-slot>
  <template v-slot:header>
    <h1>header 具名插槽</h1>
  </template>
  <template>
    <h1>默认插槽</h1>
  </template>
</my-slot>

如上代码块可以发现:

  • 子组件中的 slot标签 带上了一个名为 name 的属性,值为 header

  • 父组件中的 template标签 带上了 v-slot 的属性,值为 header

页面展示效果如图:

3. 作用域插槽(slot-scope)

再接着上述案例,添加作用域插槽 footer ,代码如下

<!-- 子组件 -->
<template>
  <div class="wrapper">
    <!-- header 具名插槽 -->
    <header class="header">
      <slot name="header"></slot>
    </header>
    <!-- 默认插槽 -->
    <div class="main">
      <slot></slot>
    </div>
    <!-- footer 具名 + 作用域插槽 -->
    <footer class="footer">
      <slot name="footer" :footerInfo="footerInfo"></slot>
    </footer>
  </div>
</template>
<script>
export default {
  name: "mySlot",
  data () {
    return {
      footerInfo: {
        text: '这是 子组件 footer插槽 的作用域数据'
      }
    }
  }
}
</script>

<!-- 父组件 -->
<my-slot>
  <template v-slot:header>
    <h1>header 具名插槽</h1>
  </template>
  <template>
    <h1>默认插槽</h1>
  </template>
  <template v-slot:footer="slotProps">
    <h1>footer 具名 + 作用域插槽</h1>
    <p>{{ slotProps.footerInfo.text }}</p>
  </template>
</my-slot>

如上代码块可以发现:

  • 子组件中的 slot标签 除了有 name=footer 的属性,还有一个:footerInfo="footerInfo" 的属性(作用就是传递子组件数据)

  • 父组件中的 template标签 不仅有 v-slot:footer ,并且还有一个赋值操作 ="slotProps",在模版的双括号语法中,直接通过 slotProps 访问到 子组件的 footerInfo

页面展示效果如图:

好了,简单回顾完用法后,笔者在这里先提三个问题:

  1. 普通插槽、 作用域插槽 的 vNode 是在哪个环节生成的,render 父组件时还是子组件时?
  2. 作用域插槽 为什么能在父组件访问到子组件的数据?
  3. 普通插槽 跟 作用域插槽 在实现上有区别吗?

我们带着疑问接着往下看!

二、不同slot的编译区别

我们根据上述最终的案例代码,执行一下打包命令,看看 Vue 在编译模板的时候,是怎么处理我们的 slot 的!事不宜迟,赶紧 build 一哈~(偷偷告诉大?,Vue 处理 作用域插槽普通插槽 的差异就是从编译开始的,也就是 render函数 会有所不同)

这里笔者顺便使用 v2.5 的具名插槽写法给大?参照一下(对具名插槽header做改写,使用 slot="header" 的写法),大家可以看下 v2.6v2.5 具名插槽的 写法、实现 上的区别~反正也不难,也就顺便带出来看看了

上图左边是 v2.6 、右边是 v2.5 的,这里,我们集中关注:

  • scopedSlots 属性。使用作用域插槽的 footer 的 render函数 是放在 scopedSlots 里的,并且 函数中 还有接收一个参数

  • my-slotchildren。可以发现,默认插槽的 render函数 一直都是作为当前组件的childre节点,放在 当前 组件render函数 的第三个参数

  • 关注 header 两种具名插槽写法的区别。

    • v2.6 中,我们使用了具名插槽,但是又未使用 作用域插槽的 header 被放在了 scopedSlots但是函数的参数为空,这点跟作用域插槽有区别
    • v2.5 中, 具名插槽header 仅仅作为 my-slot组件 的children节点,并且其render函数的第二个参数中有一个 slot 的属性。

其实根据上述编译后的结果,我们不妨这样猜测

  • 默认插槽直接在父组件的 render 阶段生成 vNode

    • 子组件 render 时,可能通过某种手段取得已经生成好的 插槽vNode 用作自己的 slot 节点。
    • 因为观察上述默认插槽的render函数:e("h1", [t._v("默认插槽")]),直接就是 my-slot 组件的childre节点(位于 my-slot 组件的第三个参数)。
  • 作用域插槽是在子组件 render 阶段生成 vNode

    • 因为我们观察作用域插槽 footer 的编译后结果,其不作为 my-slot 组件的 children,而是被放在了 my-slot 组件的 第二个参数 data 中的一个 scopedSlots属性里。
    • 并且,作用域插槽的 render 函数 外层的 funciton 接收了一个参数。如果在执行子组件 render 的时候调用,那完全可以拿到子组件的数据。

这里放出具体的 作用域插槽 打包后代码,大家一看就很清晰了:

{
  scopedSlots: t._u([
    {
      key: "footer", 
      // 函数接收了一个参数n    
      fn: function (n) {
        return [
          // h1 标签的 render 函数
          e("h1", [t._v("footer 具名 + 作用域插槽")]), 
          // p 标签的 render 函数,这里可以看到编译后是:n.footerInfo.text
          e("p", [t._v(t._s(n.footerInfo.text))])
        ]
      }
    }
  ])
}

三、slot实现原理

1. 断点调试

为了方便大家看调试结果,当前项目的组件结构主要是这样,有三大层:

Vue -> <App /> -> <my-slot />

这里笔者在运行时代码 initRender()renderSlot() 中,打上 debugger ,直接带大火看看执行流程。这里简单介绍下两个方法:

  • initRender:获取 vm.$slot 。组件初始化时执行(比如执行到 my-slot组件 时,可从vm.$slot 获取父组件中的slot vNode,也就是我们的 默认插槽)

  • renderSlot:把组件的 slot 替换成真正的 插槽vNode

接下来直接看实验截图:

1、先是进入initRender()(这里跳过初始化 大VueApp 的过程)。直接到初始化 my-slot组件 过程。【 简单解释:由于 App组件 执行 render 得到 App组件vNode ,在 patch 过程中 遇到 vue-component-my-slot 的 vNode ,又执行 my-slot组件 的 初始化流程。不是很熟悉组件化流程的朋友可以去看看笔者的Vue响应式原理~】

  • 我们不难发现,图中此时正值 my-slot组件init 阶段。

  • 再往下执行,我们可以得到 App组件中的 <h1>默认插槽</h1> 的vNode,赋值给 vm.$slot(这里我们记住,默认插槽的 vNode 已经得到)

2、再是进入 renderSlot()。接着上面继续单步执行,会走到 renderSlot 中。这时候,已经进入到 my-slot组件render 阶段了。回顾第一步中,此时我们手握 默认插槽的vNode,并存在 vm.$slot.default

header插槽

  • 按顺序走,先是 render 排第一的 header 的 vNode。如图所示,会走到断点处,我们接着单步

  • 直接进入执行我们 header插槽 的render函数执行处。根据调试步骤,我们可以肯定,放置在 scopedSlots属性 中的 render函数,是在子组件 render 的时候执行

  • 得到 header插槽 的 vNode

默认插槽

  • 继续单步走,这次轮到 默认插槽 了!如图所示,这里的 key 正是 'default'。可以发现,这里并没有像上面 header插槽 一样,去执行 render,而是直接将我们之前得到的 插槽vNode返回了。

  • 得到 default插槽 的 vNode

作用域插槽

  • 前面都跟 header插槽 一致,都是会在 my-slot 组件中 执行插槽的 render。我们直接单步到 render 处看看有什么区别。这里可以得出,function处传入的参数正是我们子组件 my-slotdata 数据,这就是为什么我们在 App组件 能通过 作用域插槽 访问到子组件数据的原因了

  • 最后也是返回 footer插槽 的vNode。好了,验证过程结束~

2. 总结插槽实现原理

其实上面的流程只是论证过程,大家不可以不必深陷其中。笔者在这里直接根据实践过程,给大伙总结出结论!也就是要回到我们一开始的三个问题!

1、普通插槽、 作用域插槽 的 vNode 是在哪个环节生成的,render 父组件时还是子组件时?

  • 默认插槽,不管 v2.5v2.6 的写法,都是在 父组件中生成 vNodevNode 存在 vm.$slot 中。待子组件 render 到插槽时,会直接拿到 父组件的 vNode

  • 具名插槽两个版本情况不一。根据编译结果可知:

    • v2.5 的写法,跟默认插槽是一样的,在父组件生成vNode,子组件直接拿来用

    • v2.6 中,直接时在 子组件 中才去执行 插槽render ,生成 插槽vNode

  • 作用域插槽。不管版本,都是在子组件中进行render的

  • 大家不妨这么理解,模版编译后,只要是被放在 scopeSlots属性 中的插槽,都会在子组件执行 render 的时候才会去生成vNode。

2、作用域插槽 为什么能在父组件访问到子组件的数据?

  • 作用域插槽只有子组件render的时候,才会执行render生成vNode。并且,作用域插槽的 render 函数能接参数,从而获得子组件的数据。就是这样形成了作用域插槽!所以我们能在父组件中,访问到子组件的data数据。

3、普通插槽 跟 作用域插槽 在实现上有区别吗?

  • 有区别。
    • 普通插槽。如果是 v2.5 ,具名插槽 和 默认插槽 都只在 父组件 render 的时候生成 vNode,子组件要 渲染slot 的时候,直接在父组件实例的 $slot 中获取已经是vNode的数据。

    • 普通插槽。如果是 v2.6 ,具名插槽 虽然是在子组件中执行的 render,但是其不接收参数

    • 作用域插槽。不管 v2.5 还是 v2.6,都只在 子组件执行 render,并且能接收参数

好了,最后来个精炼的总结。作用域插槽一定是延迟执行,且接收参数!普通插槽 可能延迟执行,可能直接执行,但不接收参数!


写在最后,很多时候我们搬砖,遵照文档把功能实现确实省力省心~但当你做多了,你就发现当前的东西缺乏挑战,索然无味。那这个时候,就会有一种冲动,想深入其实现原理,看看 slot 到底是怎么实现的。特别是作用域插槽。用的时候都会想当然的觉得在上层组件通过作用域插槽拿到子组件的数据理所应当,但是在深入源码之后,看懂了别人是怎么做的,会有突然恍然大悟的感觉~

(学习分享:web前端开发、编程基础)

以上就是聊聊Vue.slot原理,起探究下slot 是怎么实现的!的详细内容

标签: 暂无标签
jquery怎么从dom删除所有匹配的元素
« 上一篇
jquery中设置点击事件的方法是哪个
下一篇 »
  • jquery怎么在元素内部增加元素
    594710阅读
    方法:1、用“$(指定元素).prepend(新元素)”或“$(新元素).prependTo(指定元素)”,可在开头增加元素;2、用“$(指定元素).append(新元素)”或“$(新元素).appendTo(指定元素)”,在末尾增加元素。本教程操作环境:...
  • css怎么去除a标签鼠标样式
    296035阅读
    在css中,可用cursor属性去除a标签的鼠标样式,该属性用于定义鼠标指针在一个元素边界范围内所用的鼠标样式,属性值设置为none时,会去除元素的鼠标样式,设置为default时,显示默认箭头样式,语法为“a{cursor:none...
  • react的脚手架是什么意思
    39954阅读
    在react中,脚手架是快速生成项目工程化结构工具的意思;React脚手架能够快速开始一个React的项目,生成一个通用的目录结构,并配置所需环境,其中包含基础的依赖库,只需要利用“npm install”就能够安装,节省了项...
  • 浅析Bootstrap中列表组、分页和进度条组件的用法
    39545阅读
    Bootstrap中怎么实现列表组、分页和进度条?下面本篇文章给大家介绍一下Bootstrap中列表组、分页和进度条组件的用法,希望对大家有所帮助!列表组使用.list-group选择列表组添加.active到 a.list-group-item以指示当...
  • JavaScript有没有stl库
    10026阅读
    JavaScript没有stl库。stl库是标准模板库,是一个具有工业强度的高效的“C++”程序库,用于提供基本的语言本身的数据结构等扩展支持,stl库被容纳与“C++”标准程序库中,因此在JavaScript中没有stl库。本教程操作环...
  • es6的模块导出使用什么方法
    5519阅读
    es6的模块导出使用的方法:1、导出默认模块,一个模块文件只能有一个默认模块,语法为“export default 需要导出的成员”;2、导出普通模块,一个模块文件可以有多个普通模块,语法为“export 按需导出的成员”。本教...
  • css3新增长度单位的使用方法是什么
    4261阅读
    使用方法:1、em表示相对于当前对象内文本的字体大小;2、rem表示相对于根元素的字体大小;3、ch表示数字0的大小;4、vh表示百分比视口的高度;5、vw表示百分比视口的宽度;6、ex表示当前字体的小写x字母的的高度或者...
  • 在css3中可实现缩放效果的是什么属性
    4224阅读
    在css3中可实现缩放效果的是transform属性。transform属性可以和scaleX()函数实现水平方向的缩放效果、可以和scaleY()函数实现垂直方向的缩放效果、可和“scale(x,y)”函数实现水平方向和垂直方向同时缩放。本教程操...
  • 使用javascript:void(0)的原因是什么
    4171阅读
    使用“javascript:void(0)”的原因是:可以防止链接跳转到其他页面,保留链接的样式,并且不让链接执行实际操作。void关键字用于指定要计算一个表达式但是不返回值,“void(0)”也就表示不进行任何操作。本教程操作环...
  • jquery怎么设置文本框只读
    4071阅读
    方法:1、利用“$(input元素)”语句获取文本框元素对象;2、利用attr()方法设置文本框只读,将readonly属性值设置为readonly即可,语法为“文本框元素对象.attr("readonly","readonly")”。本教程操作环境:windows1...

如本文对您有帮助,随意赞赏一下!