Vue3 源码解读系列(三)——组件渲染

组件渲染

vnode 本质是用来描述 DOM 的 JavaScript 对象,它在 Vue 中可以描述不同类型的节点,比如:普通元素节点、组件节点等。

vnode 的优点:

  1. 抽象:引入 vnode,可以把渲染过程抽象化,从而使得组件的抽象能力也得到提升

  2. 跨平台:因为 patch vnode 的过程不同平台可以有自己的实现,基于 vnode 再做服务端渲染、weex 平台、小程序平台的渲染

组件的渲染流程:

在这里插入图片描述

  1. 创建 vnode

    createVNode 主要做了四件事:

    1. 处理 props,标准化 class 和 style
    2. 对 vnode 类型信息编码
    3. 创建 vnode 对象
    4. 标准化子节点
    /**
     * 创建 vnode
     */
    function createVNode(type, props = null, children = null) {
      // 1、处理 props,标准化 class 和 style
      if (props) {
        // ...
      }
    
      // 2、对 vnode 类型信息编码
      const shapeFlag = isString(type)
        ? 1 /* ELEMENT */
        : isSuspense(type)
          ? 128 /* SUSPENSE */
          : isTeleport(type)
            ? 64 /* TELEPORT */
            : isObject(type)
              ? 4 /* STATEFUL_COMPONENT */
              : isFunction(type)
                ? 2 /* FUNCTIONAL_COMPONENT */
                : 0
    
      // 3、创建 vnode 对象
      const vnode = {
        type,
        props,
        shapeFlag,
        // 一些其他属性
      }
    
      // 4、标准化子节点,把不同数据类型的 children 转成数组或者文本类型
      normalizeChildren(vnode, children)
      return vnode
    }
    
  2. 渲染 vnode

    render 主要做了几件事:

    1. 检查是否存在 vnode
      • 如果之前有,现在没有,则销毁
      • 如果现在有,则创建或更新
    2. 缓存 vnode,用于判断是否已经渲染
    /**
     * 渲染 vnode
     */
    const render = (vnode, container) => {
      // vnode 为 null,则销毁组件
      if (vnode == null) {
        if (container._vnode) {
          unmount(container._vnode, null, null, true)
        }
      }
      // 否则创建或者更新组件
      else {
        patch(container._vnode || null, vnode, container)
      }
    
      // 缓存 vnode 节点,表示已经渲染
      container._vnode = vnode
    }
    

    patch 主要做了两件事:

    1. 判断是否销毁节点
    2. 挂载新节点
    /**
     * 更新 DOM
     * @param {vnode} n1 - 旧的 vnode(为 null 时表示第一次挂载)
     * @param {vnode} n2 - 新的 vnode
     * @param {DOM} container - DOM 容器,vnode 渲染生成 DOM 后,会挂载到 container 下面
     */
    const patch = (n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, optimized = false) => {
      // 如果存在新旧节点,且新旧节点类型不同,则销毁旧节点
      if (n1 && !isSameVNodeType(n1, n2)) {
        anchor = getNextHostNode(n1)
        unmount(n1, parentComponent, parentSuspense, true)
        n1 = null
      }
    
      // 挂载新 vnode
      const { type, shapeFlag } = n2
      switch (type) {
        case Text:
          // 处理文本节点
          break
        case Comment:
          // 处理注释节点
          break
        case Static:
          // 处理静态节点
          break
        case Fragment:
          // 处理 Fragment 元素
          break
        default:
          if (shapeFlag & 1/* ELEMENT */) {
            // 处理普通 DOM 元素
            processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)
          } else if (shapeFlag & 6/* COMPONENT */) {
            // 处理 COMPONENT
            processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)
          } else if (shapeFlag & 64/* TELEPORT */) {
            // 处理 TELEPORT
          } else if (shapeFlag & 128/* SUSPENSE */) {
            // 处理 SUSPENSE
          }
      }
    }
    

    处理组件

    /**
     * 处理 COMPONENT
     */
    const processComponent = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
      // 旧节点为 null,表示不存在旧节点,则直接挂载组件
      if (n1 == null) {
        mountComponent(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)
      }
      // 旧节点存在,则更新组件
      else {
        updateComponent(n1, n2, parentComponent, optimized)
      }
    }
    
    /**
     * 挂载组件
     * mountComponent 做了三件事:
     * 1、创建组件实例
     * 2、设置组件实例
     * 3、设置并运行带副作用的渲染函数
     */
    const mountComponent = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
      // 1、创建组件实例,内部也通过对象的方式去创建了当前渲染的组件实例
      const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent, parentSuspense))
    
      // 2、设置组件实例,instance 保留了很多组件相关的数据,维护了组件的上下文包括对 props、插槽,以及其他实例的属性的初始化处理
      setupComponent(instance)
    
      // 3、设置并运行带副作用的渲染函数
      setupRenderEffet(instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized)
    }
    
    /**
     * 初始化渲染副作用函数
     * 副作用:当组件数据发生变化时,effect 函数包裹的内部渲染函数 componentEffect 会重新执行一遍,从而达到重新渲染组件的目的
     */
    const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
      // 创建响应式的副作用渲染函数
      instance.update = effect(function componentEffect() {
        // 如果组件实例 instance 上的 isMounted 属性为 false,说明是初次渲染
        /**
         * 初始化渲染主要做两件事情:
         * 1、渲染组件生成子树 subTree
         * 2、把 subTree 挂载到 container 中
         */
        if (!instance.isMounted) {
          // 1、渲染组件生成子树 vnode
          const subTree = (instance.subTree = renderComponentRoor(instance))
    
          // 2、把子树 vnode 挂载到 container 中
          patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)
    
          // 保留渲染生成的子树根 DOM 节点
          initialVNode.el = subTree.el
          instance.isMounted = true
        }
        // 更新组件
        else {
          // ...
        }
      }, prodEffectOptions)
    }
    

    处理普通元素

    /**
     * 处理 ELEMENT
     */
    const processElement = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
      isSVG = isSVG || n2.type === 'svg'
      // 旧节点为 null,说明没有旧节点,为第一次渲染,则挂载元素节点
      if (n1 == null) {
        mountElement(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)
      }
      // 否则更新元素节点
      else {
        patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized)
      }
    }
    
    /**
     * 挂载元素
     * mountElement 主要做了四件事:
     * 1、创建 DOM 元素节点
     * 2、处理 props
     * 3、处理子节点
     * 4、把创建的 DOM 元素节点挂载到 container 上
     */
    const mountElement = (vnode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
      let el
      const { type, props, shapeFlag } = vnode
    
      // 1、创建 DOM 元素节点
      el = vnode.el = hostCreateElement(vnode.type, isSVG, props && props.is)
    
      // 2、处理 props,比如 class、style、event 等属性
      if (props) {
        // 遍历 props,给这个 DOM 节点添加相关的 class、style、event 等属性,并作相关的处理
        for (const key in props) {
          if (!isReservedProp(key)) {
            hostPatchProp(el, key, null, props[key], isSVG)
          }
        }
      }
    
      // 3、处理子节点
      // 子节点是纯文本的情况
      if (shapeFlag & 8/* TEXT_CHILDREN */) {
        hostSetElementText(el, vnode.children)
      }
      // 子节点是数组的情况
      else if (shapeFlag & 16/* ARRAY_CHILDREN */) {
        mountChildren(vnode.children, el, null, parentComponent, parentSuspense, isSVG && type !== 'foreignObject', optimized || !!vnode.dynamicChildren)
      }
    
      // 4、把创建的 DOM 元素节点挂载到 container 上
      hostInsert(el, container, anchor)
    }
    
    /**
     * 创建元素
     */
    function createElement(tag, isSVG, is) {
      // 在 Web 环境下的方式
      isSVG ? document.createElementNS(svgNS, tag) : document.createElement(tag, is ? { is } : undefined)
    
      // 如果是其他平台就不是操作 DOM 了,而是平台相关的 API,这些相关的方法是在创建渲染器阶段作为参数传入的
    }
    
    /**
     * 处理子节点是纯文本的情况
     */
    function setElementText(el, text) {
      // 在 Web 环境下通过设置 DOM 元素的 textContent 属性设置文本
      el.textContent = text
    }
    
    /**
     * 处理子节点是数组的情况
     */
    function mountChildren(children, container, anchor, parentComponent, parentSuspense, isSVG, optimized, start = 0) {
      // 遍历 chidren,获取每一个 child,递归执行 patch 方法挂载每一个 child
      for (let i = start; i < children.length; i++) {
        // 预处理 child
        const child = (children[i] = optimized ? cloneIfMounted(children[i]) : normalizeVNode(children[i]))
    
        // 执行 patch 挂载 child
        // 执行 patch 而非 mountElement 的原因:因为子节点可能有其他类型的 vnode,比如 组件 vnode
        patch(null, child, container, anchor, parentComponent, parentSuspense, isSVG, optimized)
      }
    }
    
    /**
     * 把创建的 DOM 元素节点挂载到 container 下
     * 因为 insert 的执行是在处理子节点后,所以挂载的顺序是先子节点,后父节点,最终挂载到最外层的容器上
     */
    function insert(child, parent, anchor) {
      // 如果有参考元素 anchor,则把 child 插入到 anchor 前
      if (anchor) {
        parent.insertBefore(child, anchor)
      }
      // 否则直接通过 appendChild 插入到父节点的末尾
      else {
        parent.appendChild(child)
      }
    }
    

扩展:嵌套组件

组件 vnode 主要维护着组件的定义对象,组件上的各种 props,而组件本身是一个抽象节点,它自身的渲染其实是通过执行组件定义的 render 渲染函数生成的子树 vnode 来完成,然后再通过 patch 这种递归的方式,无论组件的嵌套层级多深,都可以完成整个组件树的渲染。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/133736.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

用 winget 在 Windows 上安装 kubectl

目录 kubectl 是什么&#xff1f; 安装 kubectl 以管理员身份打开 PowerShell 使用 winget 安装 kubectl 测试一下&#xff0c;确保安装的是最新版本 导航到你的 home 目录&#xff1a; 验证 kubectl 配置 kubectl 是什么&#xff1f; kubectl 是 Kubernetes 的命令行工…

38 路由的过滤器配置

3.3.断言工厂 我们在配置文件中写的断言规则只是字符串&#xff0c;这些字符串会被Predicate Factory读取并处理&#xff0c;转变为路由判断的条件 例如Path/user/**是按照路径匹配&#xff0c;这个规则是由 org.springframework.cloud.gateway.handler.predicate.PathRoute…

计算机毕业设计:水果识别检测系统 python 深度学习 YOLOv5

[毕业设计]2023-2024年最新最全计算机专业毕设选题推荐汇总 感兴趣的可以先收藏起来&#xff0c;还有大家在毕设选题&#xff0c;项目以及论文编写等相关问题都可以给我留言咨询&#xff0c;希望帮助更多的人 。 1、项目介绍 本文介绍了一种基于深度学习的水果检测与识别系统…

移动端模型部署框架

移动端模型部署框架 1. MNN整体特点轻量性通用性高性能易用性架构设计主体工具致谢移动端模型部署框架 1. MNN https://www.yuque.com/mnn/cn/about MNN是全平台轻量级高性能深度学习引擎,广泛支持了阿里巴巴在计算机视觉、语音识别技术、自然语言处理等领域的70多个AI应用…

【Java 进阶篇】Java中的 JSP(JavaServer Pages)

JavaServer Pages&#xff08;JSP&#xff09;是一种用于开发动态Web页面的Java技术。它是在静态Web页面中嵌入Java代码的一种方式&#xff0c;使得开发者可以借助Java的强大功能来创建动态、交互性强的Web应用程序。在本文中&#xff0c;我们将深入探讨JSP的概念、原理和基本用…

MYSQL内容补充:

一)联合索引: 1)定义:是给一张表上面的多个列增加索引&#xff0c;也就是说给表上面的多个列增加索引&#xff0c;供快速查询使用&#xff0c;当两个列的组合是唯一值时&#xff0c;联合索引是个不错的选择 联合索引和单个索引对比来讲&#xff0c;联合索引的所有索引项都会出现…

重温数据结构与算法之前缀和

文章目录 前言一、基础1.1 定义1.2 时间复杂度 二、扩展2.1 二维前缀和2.2 差分数组2.3 前缀积 三、LeetCode 实战3.1 长度最小的子数组3.2 二维区域和检索 - 矩阵不可变 参考 前言 前缀和&#xff08;Prefix Sum&#xff09;&#xff0c;也被称为累计和&#xff0c;是一种在计…

SQL必知会(二)-SQL查询篇(5)-用通配符进行过滤

第6课、用通配符进行过滤 LIKE&#xff1a;匹配文本 LIKE&#xff1a;针对未知值进行过滤。通配符搜索只能用于文本字段。 1&#xff09;百分号%通配符 %表示任何字符出现任意次数。 需求&#xff1a;找出所有以词 Fish 起头的产品 SELECT prod_id, prod_name FROM Product…

浅谈高并发以及三大利器:缓存、限流和降级

引言 高并发背景 互联网行业迅速发展&#xff0c;用户量剧增&#xff0c;系统面临巨大的并发请求压力。 软件系统有三个追求&#xff1a;高性能、高并发、高可用&#xff0c;俗称三高。三者既有区别也有联系&#xff0c;门门道道很多&#xff0c;全面讨论需要三天三夜&#…

Aria2 任意文件写入漏洞复现

漏洞描述 Aria2 是一款轻量级、多协议、多源下载工具&#xff08;支持 HTTP/HTTPS、FTP、BitTorrent、Metalink&#xff09;&#xff0c;内置 XML-RPC 和 JSON-RPC 接口。 我们可以使用 RPC 接口来操作 aria2 并将文件下载到任意目录&#xff0c;从而造成任意文件写入漏洞。 …

Nginx常用配置与命令,nginx代理转发配置

Nginx特点 高并发、高性能; 模块化架构使得它的扩展性非常好; 异步非阻塞的事件驱动模型这点和 Node.js 相似; 相对于其它服务器来说它可以连续几个月甚至更长而不需要重启服务器使得它具有高可靠性; 热部署、平滑升级; 完全开源,生态繁荣; Nginx作用 Nginx 的最重要的…

【Excel】补全单元格值变成固定长度

我们知道股票代码都为6位数字&#xff0c;但深圳中小板代码前面以0开头&#xff0c;数字格式时前面的0会自动省略&#xff0c;现在需要在Excel表格补全它。如下图&#xff1a; 这时我们需要用到特殊的函数&#xff1a;TEXT或者RIGHT TEXT函数是Excel中一个非常有用的函数。TEX…

SpringBoot项目调用openCV报错:nested exception is java.lang.UnsatisfiedLinkError

今天在通过web项目调用openCV的时候提示如下错误&#xff1a; nested exception is java.lang.UnsatisfiedLinkError:org.opencv.imgcodecs.Imgcodecs.imread_0(Ljava/la如下图所示&#xff1a; 但是通过直接启动java main函数确正常&#xff0c;初步诊断和SpringBoot热加载…

dart packages 版本问题解决 和 对 pubspenc.lock 的深入了解

先讲讲我遇到的问题 在进行写项目的时候&#xff0c;我需要用到一个依赖 这个依赖是 3.0.0 版本的&#xff0c;但是实际上我本地的上存在多个版本 虽然我修改了 pubspenc.yaml 文件中需要的依赖&#xff0c;但是每次使用的还是 3.4.0 版本的依赖&#xff0c;但是我需要的是 3…

Windows查看端口占用情况

Windows如何查看端口占用情况 方法1. cmd命令行执行netstat命令&#xff0c;查看端口占用情况 netstat -ano 以上命令输出太多信息&#xff0c;不方便查看&#xff0c;通过如下命令搜索具体端口占用情况&#xff0c;例如&#xff1a;8080端口 netstat -ano | findstr "…

手摸手入门Springboot+Grafana10.2接收JSON

JSON&#xff08;JavaScript Object Notation, JS对象简谱&#xff09;是一种轻量级的数据交换格式。它基于 ECMAScript&#xff08;European Computer Manufacturers Association, 欧洲计算机协会制定的js规范&#xff09;的一个子集&#xff0c;采用完全独立于编程语言的文本…

面向对象基础(以python语言为例)

1、定义一个类&#xff1b;实例化类的对象&#xff1b;调用类中的方法 #定义一个类 class Student:#类方法&#xff08;即函数&#xff09;def study(self,course_name):print(f学生正在学习{course_name})def play(self):print("xx学生正在玩游戏")#实例化&#xf…

Rust的崛起:现代必备编程语言,是时候该考虑加入学习了

在不断变化的编程环境中&#xff0c;新的语言和框架如雨后春笋般涌现&#xff0c;需要一个真正强大且设计良好的工具才能脱颖而出。在这些工具中&#xff0c;Rust 已成为效率、安全性和性能的灯塔。从它作为 Mozilla 的一个副项目到它在软件行业中不可否认的增长&#xff0c;Ru…

【沐风老师】3DMAX克隆修改器插件教程

3DMAX克隆修改器插件&#xff0c;它通过增量平移、旋转和缩放输入几何体来创建对象的副本。在某些方面&#xff0c;它类似于 3ds Max 的内置阵列工具&#xff0c;但有一个主要优点 -克隆是完全参数化的&#xff0c;因此您可以随时更改重复项的数量及其分布。其他功能包括随机变…

基于单片机的空调智能控制器的设计

**单片机设计介绍&#xff0c;基于单片机的空调智能控制器的设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的空调智能控制器需要具备输入输出端口、定时器、计数器等模块&#xff0c;以便对空调进行精确控制。下…