当前所在位置: 首页 > 儿童教育 > 正文

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

2023-02-01 餐饮美食网 【 字体:

本篇文章给大家分享一下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 是怎么实现的!的详细内容

阅读全文
相关推荐

如何自制蛋挞

如何自制蛋挞
1、倒入低筋面、黄油和水,揉成面团状,藏一小时后取出,抹上黄油卷起,包上保鲜膜冷藏半小时。2、取出,将面团切成一厘米的面团,放入模具内,用力按压,中间稍薄,外缘要比模具高,将蛋挞液倒入蛋挞皮中,放入烤箱里烤30分钟即可。

肉火烧面怎么和面

肉火烧面怎么和面
1、首先用温水把酵母融开,加入面粉,用手把面粉揉成非常软的面团,可以放一点熬好的猪油在面团里面。2、然后放在温暖的地方,盖上保鲜膜发酵至2倍大,发好的面团用手插入不回缩,就说明面团发酵好了。

奶茶几分糖好喝

奶茶几分糖好喝
1、不同的人对奶茶的口感要求是不一样的,一般来说,奶茶有三分糖、五分糖、七分糖三种不同的口味。2、女生一般都喜欢喝甜一点的,而男生则喜欢喝不那么甜的,七分糖的奶茶最好喝。

过桥米线是哪里的?

过桥米线是哪里的?
过桥米线是来自云南省滇南地区的一种有名特色小吃。过桥米线最早是在清朝就已经出现,距离现在至少有一百多年的历史啦!起源于建水县东城外锁龙桥西侧的鸡市街头处,有建水的特产草芽、地椒作配料,风味独特而远近闻名。深受广大美食爱好者的喜欢,不少人为此专门长途跋涉,只为尝

面霜和乳液的区别

面霜和乳液的区别
面霜和乳液的区别,相较而言乳液的水分含量要比面霜高,乳液质地要轻薄一些,乳液主要作用是保湿,滋润可以隔离外界干燥的气候,面霜既可保湿,又可美白,还能抗衰老,乳液的吸收快一些,而面霜吸收比较慢一些,因为液体的吸收速度都比较快。

怎么摘隐形眼镜

怎么摘隐形眼镜
在摘隐形眼镜之前,先用洗手液将手清洗干净,以免将细菌带入眼睛内,对着镜子,用右手中指轻拉眼睛下眼睑,左手中指轻拉眼睛上眼睑,让黑色眼球暴露在空气中,用右手食指和拇指轻触镜片的两边缘部分,向中间推使镜片拱起,再用两手指轻轻捏出镜片即可。

粉饼和散粉的区别

粉饼和散粉的区别
粉饼是呈压缩固体状态,多呈圆形或者方形,散粉则是细腻的粉末状,粉饼遮瑕力会比较强一些,可以湿用做粉底,或者用来补妆,而散粉则是定妆的效果,粉饼通常用在底妆的第一步,而散粉通常用在底妆最后一步。

冷烫和热烫的区别

冷烫和热烫的区别
冷烫和热烫的区别:冷烫对头发的要求是要在保温状态下才能给卷有个好的效果,而热烫是在干和湿的情况下都行,热烫烫出来的头发比较自然明显而有弹力,冷烫的头发风干后基本看不出来,并且发质会有点干。

高品质香水如何鉴别?用三步就可以解决

高品质香水如何鉴别?用三步就可以解决
1、看色泽以天然香料调制而成的高级香水,都有它本来的颜色,且大都是琥珀色或褐色,看起来很像宝石,比如,从茉莉、玫瑰或水仙等天然鲜花中所萃取的精油都呈黄色、褐白或绿褐色;此外,香水中所添加的魅惑香气物性香料也是褐色,苔类中的橡树苔是绿色,从树根或树根脂类中萃取的

如何去除黑眼圈

如何去除黑眼圈
去除黑眼圈首先可以用热毛巾敷眼,然后再用冷毛巾敷,十分钟就能让黑眼圈淡化的不那么严重,其次将去壳后的鸡蛋用无菌纱布包裹住,敷于眼部轻轻转动,可以急救去除黑眼圈,另外抹完眼霜后,用双手顺时针按摩,可以促进血液循环消除黑眼圈。
本文Tag