Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vue之关于slot #32

Open
doreenChenD opened this issue Aug 30, 2019 · 0 comments
Open

vue之关于slot #32

doreenChenD opened this issue Aug 30, 2019 · 0 comments

Comments

@doreenChenD
Copy link
Contributor

doreenChenD commented Aug 30, 2019

vue之关于slot(内容分发、插槽)(一)

这篇是个善良的搬运工

目录

  • 用法
  • 普通插槽 实现过程

用法

vue组件的一个非常重要的特性——slot 插槽,相信大家不陌生。slot使组件的使用更加灵活,父子组件之间的通信也更多样化。插槽分为普通插槽和作用域插槽。

普通插槽

特点:作用域为父组件的作用域,而不是子组件的。即可以访问父组件的属性,而不能访问子组件的属性。
使用方法:

父组件

image

子组件

image

后备内容

特点:为一个插槽设置具体的后备 (也就是默认的) 内容是很有用的,它只会在没有提供内容的时候被渲染。
image

具名插槽

特点: 将不同的slot的放到子组件对应的位置

  1. slot (2.6.0起被废弃)
    image
    不带name的slot 将会隐含名字default

  2. v-slot (2.6.0 起使用)
    v-slot 只能写在template或者component上
    image

作用域插槽

特点: 父组件访问子组件的数据

  1. slot-scope (2.6.0起被废弃) 2.5+ 可以用在任何标签 2.5以前只能用在template标签
    image

  2. v-slot (2.6.0后使用)
    image
    作用域插槽的内部工作原理是将你的插槽内容包括在一个传入单个参数的函数里,这意味着 v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式。
    image

3.动态插槽名
image

  1. 缩写与注意
    default缩写:
    image

v-slot缩写,该缩写只在其有参数的时候才可用 (#后面必须有插槽名)
image

注意:
image

5 .插槽 prop 允许我们将插槽转换为可复用的模板,这些模板可以基于输入的 prop 渲染出不同的内容。
image

6.无渲染组件
image
image
image
无渲染组件——大漠

实现过程 (以slot 为例)

vue中的slot 源于web components 规范草案,是组件的一块HTML模板。那么slot的实现过程是怎么样的呢?

编译作用域

在Vue中,组件是有一个作用域的:组件模板()内的就是组件作用域,而其之外的就不是组件的作用域了。组件的模板是在其作用域内编译的。
父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译。
slot 是父组件向子组件分发内容,所以slot里的html 的编译作用域是父组件的编译作用域。

slot 实现过程

slot 本质是返回Vnodede的函数。
在2.5之前,如果是普通插槽就直接是VNode的形式了,而如果是作用域插槽,由于子组件需要在父组件访问子组件的数据,所以父组件下是一个未执行的函数
(slotScope) => return h('div', slotScope.msg)
,接受子组件的slotProps参数,在子组件渲染实例时会调用该函数传入数据。

开始

源码基础:
Vue2.1.7源码学习
├── build --------------------------------- 构建相关的文件,一般情况下我们不需要动
├── dist ---------------------------------- 构建后文件的输出目录
├── examples ------------------------------ 存放一些使用Vue开发的应用案例
├── flow ---------------------------------- 类型声明,使用开源项目 Flow
├── package.json -------------------------- 不解释
├── test ---------------------------------- 包含所有测试文件
├── src ----------------------------------- 这个是我们最应该关注的目录,包含了源码
│ ├── entries --------------------------- 包含了不同的构建或包的入口文件
│ │ ├── web-runtime.js ---------------- 运行时构建的入口,输出 dist/vue.common.js 文件,不包含模板(template)到render函数的编译器,所以不支持 template 选项,我们使用vue默认导出的就是这个运行时的版本。大家使用的时候要注意
│ │ ├── web-runtime-with-compiler.js -- 独立构建版本的入口,输出 dist/vue.js,它包含模板(template)到render函数的编译器
│ │ ├── web-compiler.js --------------- vue-template-compiler 包的入口文件
│ │ ├── web-server-renderer.js -------- vue-server-renderer 包的入口文件
│ ├── compiler -------------------------- 编译器代码的存放目录,将 template 编译为 render 函数
│ │ ├── parser ------------------------ 存放将模板字符串转换成元素抽象语法树的代码
│ │ ├── codegen ----------------------- 存放从抽象语法树(AST)生成render函数的代码
│ │ ├── optimizer.js ------------------ 分析静态树,优化vdom渲染
│ ├── core ------------------------------ 存放通用的,平台无关的代码
│ │ ├── observer ---------------------- 反应系统,包含数据观测的核心代码
│ │ ├── vdom -------------------------- 包含虚拟DOM创建(creation)和打补丁(patching)的代码
│ │ ├── instance ---------------------- 包含Vue构造函数设计相关的代码
│ │ ├── global-api -------------------- 包含给Vue构造函数挂载全局方法(静态方法)或属性的代码
│ │ ├── components -------------------- 包含抽象出来的通用组件
│ ├── server ---------------------------- 包含服务端渲染(server-side rendering)的相关代码
│ ├── platforms ------------------------- 包含平台特有的相关代码
│ ├── sfc ------------------------------- 包含单文件组件(.vue文件)的解析逻辑,用于vue-template-compiler包
│ ├── shared ---------------------------- 包含整个代码库通用的代码

例子

image
这里我们定义了 AppLayout 子组件,它内部定义了 3 个插槽,2 个为具名插槽,一个 nameheader,一个 namefooter,还有一个没有定义 name 的是默认插槽。
最终生成
image

编译

组件的编译发生在调用 vm.$mount 的时候,所以编译顺序是先父组件,再子组件。
编译父组件时,parse 阶段,会执行 processSlot 处理 slot,它的定义在 src/compiler/parser/index.js 中:
image

当解析到标签上有 slot 属性的时候,会给对应的 AST 元素节点添加 slotTarget 属性,然后在 codegen 阶段,在 genData 中会处理 slotTarget,相关代码在 src/compiler/codegen/index.js 中:
image

会给 data 添加一个 slot 属性,并指向 slotTarget,之后会用到。在我们的例子中,父组件最终生成的代码如下:
image

接下来编译子组件,同样在 parser 阶段会执行 processSlot 处理函数,它的定义同样在 src/compiler/parser/index.js 中:
image

当遇到 slot 标签的时候会给对应的 AST 元素节点添加 slotName 属性,然后在 codegen 阶段,会判断如果当前 AST 元素节点是 slot 标签,则执行 genSlot 函数,它的定义在 src/compiler/codegen/index.js 中:
我们先不考虑 slot 标签上有 attrs 以及 v-bind 的情况,那么它生成的代码实际上就只有:
image
这里的 slotName 从 AST 元素节点对应的属性上取,默认是 default,而 children 对应的就是 slot 开始和闭合标签包裹的内容(默认显示的部分)。来看一下我们例子的子组件最终生成的代码,如下:
image

其中: _t 函数对应的就是 renderSlot 方法:
image
它的定义在 src/core/instance/render-heplpers/render-slot.js 中:
image
image
renderSlot 的参数 name 代表插槽名称 slotNamefallback 代表插槽的默认内容生成的 vnode 数组。
如果 this.$slot[name] 有值,就返回它对应的 vnode 数组,否则返回 fallback。那么这个 this.$slot 是哪里来的呢?我们知道子组件的 init 时机是在父组件执行 patch 过程的时候,那这个时候父组件已经编译完成了。并且子组件在 init 过程中会执行 initRender 函数,initRender 的时候获取到 vm.$slot,相关代码在 src/core/instance/render.js 中:
image

vm.$slots 是通过执行 resolveSlots(options._renderChildren, renderContext) 返回的,它的定义在 src/core/instance/render-helpers/resolve-slots.js 中:

image
resolveSlots 方法接收 2 个参数,第一个参数 chilren 对应的是父 vnode children,在我们的例子中就是 和 包裹的内容。第二个参数 context 是父 vnode 的上下文,也就是父组件的 vm 实例。

resolveSlots 函数的逻辑就是遍历 chilren,拿到每一个 child 的 data,然后通过 data.slot 获取到插槽名称,这个 slot 就是我们之前编译父组件在 codegen 阶段设置的 data.slot。接着以插槽名称为 key 把 child 添加到 slots 中,如果 data.slot 不存在,则是默认插槽的内容,则把对应的 child 添加到 slots.defaults 中。这样就获取到整个 slots,它是一个对象,key 是插槽名称,value 是一个 vnode 类型的数组,因为它可以有多个同名插槽。

2.6+ 的优化

  1. 使用 v-slot 合并 slot 和scoped slot的使用,更明确和具有更高可读性。
  2. 性能改进: 普通 slot 将在父组件的渲染周期中渲染。当 slot 的依赖项发生变化时,会导致父组件和子组件进行重新渲染。而 scoped slot 被编译成​​内联函数,并在子组件的渲染周期中被调用。
    这意味着子组件将会收集 scoped slot 所依赖的所有数据依赖项,从而做出更精确的更新。在 2.6 版本中,Vue进一步确保父组件的依赖项变化仅影响到父组件,如果只使用了 scoped slot,就不会强制子组件做出更新。
  3. 所有使用新 v-slot 语法的 slot 都将被编译为 scoped slot,这意味着所有使用新语法的 slot 都会自动获得性能提升

参考文章:
Vue 2.0学习笔记:Vue组件内容分发(slot)——大漠
vue.js技术揭秘
Vue.js文档
无渲染组件——大漠

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant