美文网首页
[搬运]聊聊Vue.slot原理

[搬运]聊聊Vue.slot原理

作者: 薯条你哪里跑 | 来源:发表于2022-11-15 15:44 被阅读0次

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

image

前端(vue)入门到精通课程:[进入学习](javascript:void(0);)
Apipost = Postman + Swagger + Mock + Jmeter 超好用的API调试工具:点击使用

相信不管是日常业务开发,还是封装基础组件,插槽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>

页面展示效果如图:

image.png

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

页面展示效果如图:

image.png

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

页面展示效果如图:

image.png

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

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

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

二、不同slot的编译区别

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

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

image.png

上图左边是 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 阶段。
image.png
  • 再往下执行,我们可以得到 App组件中的 <h1>默认插槽</h1> 的vNode,赋值给 vm.$slot(这里我们记住,默认插槽的 vNode 已经得到)
image.png

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

header插槽

  • 按顺序走,先是 render 排第一的 header 的 vNode。如图所示,会走到断点处,我们接着单步
image.png
  • 直接进入执行我们 header插槽 的render函数执行处。根据调试步骤,我们可以肯定,放置在 scopedSlots属性 中的 render函数,是在子组件 render 的时候执行
image.png
  • 得到 header插槽 的 vNode
image.png

默认插槽

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

作用域插槽

  • 前面都跟 header插槽 一致,都是会在 my-slot 组件中 执行插槽的 render。我们直接单步到 render 处看看有什么区别。这里可以得出,function处传入的参数正是我们子组件 my-slotdata 数据,这就是为什么我们在 App组件 能通过 作用域插槽 访问到子组件数据的原因了
image.png
  • 最后也是返回 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,并且能接收参数

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


相关文章

  • 深圳福田

    有兴趣一起聊聊吗?我是人民币的搬运工

  • Dubbo架构

    ☆聊聊Dubbo(三):架构原理 - 简书

  • 江苏电信开通 VoLTE 9.21 可用

    搬运,原帖:http://bbs.8080.net/thread-881807-1-1.html 主要原理是利用 ...

  • 聊聊Git原理

    说起Git,相信大家都很熟悉了,毕竟作为程序猿,每天的业余时间除了吃饭睡觉就是逛一下全世界最大的开(tong)源(...

  • 聊聊HashMap原理

    能力要进阶,必然就要多了解了解原理。看了一些Java的数据结构,简单的总结下HashMap原理。所谓map就是一个...

  • ARKit 在模型上播放视频(附进度控制)

    国际惯例先上效果图 原理 代码 代码 我的博客即将搬运同步至腾讯云+社区

  • 为什么会有出轨?

    文:蔡垒磊 ................................. 01 今天跟大家聊聊出轨的原理。 ...

  • pytorch三件套

    搬运记录一下 pytorch 构建神经网络的必经步骤及其原理: optimizer.zero_grad()#先将梯...

  • webpack的加载顺序

    搬运全栈弄潮儿的文章webpack之loader执行顺序及原理[https://www.jianshu.com/p...

  • JVM好文搬运:ClassLoader 加载原理

    JAVA 源码编译机制在搬运中。。。 类加载机制 精华都在这里面 http://blog.csdn.net/xya...

网友评论

      本文标题:[搬运]聊聊Vue.slot原理

      本文链接:https://www.haomeiwen.com/subject/wvxexdtx.html