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 和 styleif (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}// 挂载新 vnodeconst { type, shapeFlag } = n2switch (type) {case Text:// 处理文本节点breakcase Comment:// 处理注释节点breakcase Static:// 处理静态节点breakcase Fragment:// 处理 Fragment 元素breakdefault:if (shapeFlag & 1/* ELEMENT */) {// 处理普通 DOM 元素processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)} else if (shapeFlag & 6/* COMPONENT */) {// 处理 COMPONENTprocessComponent(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、渲染组件生成子树 vnodeconst subTree = (instance.subTree = renderComponentRoor(instance))// 2、把子树 vnode 挂载到 container 中patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)// 保留渲染生成的子树根 DOM 节点initialVNode.el = subTree.elinstance.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 elconst { 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 方法挂载每一个 childfor (let i = start; i < children.length; i++) {// 预处理 childconst child = (children[i] = optimized ? cloneIfMounted(children[i]) : normalizeVNode(children[i]))// 执行 patch 挂载 child// 执行 patch 而非 mountElement 的原因:因为子节点可能有其他类型的 vnode,比如 组件 vnodepatch(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 这种递归的方式,无论组件的嵌套层级多深,都可以完成整个组件树的渲染。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Oracle迁移(RAC变单机模式)

1.升级内核 systemctl stop firewalld systemctl disable firewalldrpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpm yum --enablerepo"elrepo-kernel" list --showduplic…

鸿蒙原生应用开发-DevEco Studio远程模拟器的使用

使用单设备模拟器运行应用/服务 Remote Emulator支持Phone、Wearable、Tablet、TV等设备类型&#xff0c;但不同区域&#xff08;开发者帐号注册地&#xff09;支持的设备类型可能不同&#xff0c;请以实际可申请的设备类型为准。 Remote Emulator中的单设备模拟器&#xff08…

图扑智慧农业:农林牧数据可视化监控平台

数字农业是一种现代农业方式&#xff0c;它将信息作为农业生产的重要元素&#xff0c;并利用现代信息技术进行农业生产过程的实时可视化、数字化设计和信息化管理。能将信息技术与农业生产的各个环节有机融合&#xff0c;对于改造传统农业和改变农业生产方式具有重要意义。 图…

语音识别与自然语言处理(NLP):技术前沿与未来趋势

语音识别与自然语言处理&#xff08;NLP&#xff09;&#xff1a;技术前沿与未来趋势 随着科技的快速发展&#xff0c;语音识别与自然语言处理&#xff08;NLP&#xff09;技术逐渐成为人工智能领域的研究热点。这两项技术的结合&#xff0c;使得机器能够更好地理解和处理人类语…

Leetcode Hot100之六:42.接雨水

题目 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 提示&#xff1a; n height.length 1 < n < 2 * 10^4 0 < height[i] < 10^5 思路 暴力循环&#xff1a; 原本的思路是左边界i从左到…

C语言--有3个候选人,每个选民只能投票选一人,要求编一个统计选票的程序,先后输入被选人的名字,最后输出各人得票结果。

一.解体思路 设一个结构体数组&#xff0c;数组中包含3个元素; 每个元素中的信息应包括候选人的姓名和得票数;输入被选人的姓名&#xff0c;然后与数组元素中的“姓名”成员比较&#xff0c;如果相同&#xff0c;就给这个元素中的“得票数”成 员的值加1;输出所有元素的信息。 …

[Linux打怪升级之路]-信号的保存和递达

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、信号的保…

设计模式是测试模式咩?

设计模式和测试模式概述 软件的生命周期为什么要进行测试&#xff08;测试的目的&#xff09;&#xff1f;软件的设计模式1. **瀑布模型**3. 增量和迭代模型4. 敏捷模型5. 喷泉模型 测试模型V模型W模型 一个应用程序从出生到“死亡”会经过非常漫长的流程…… 软件的生命周期 …

从windows iso文件中提取install.wim

1、首先从微软官方下载需要的windows镜像 https://www.microsoft.com/zh-cn/software-download/windows10/ 2、在下载的iso文件右键&#xff0c;打开压缩包&#xff0c;在sources文件夹下&#xff0c;应该就可以看到install.wim了。但似乎在最新的win10版本&#xff0c;微软采…

Vue3使用vue-print-nb插件打印功能

插件官网地址https://www.npmjs.com/package/vue-print-nb 效果展示: 打印效果 根据不同的Vue版本安装插件 //Vue2.0版本安装方法 npm install vue-print-nb --save pnpm install vue-print-nb --save yarn add vue-print-nb//Vue3.0版本安装方法&#xff1a; npm install vue3…

低代码平台,业务开发的“银弹”

目录 一、为什么需要低代码平台 二、低代码平台的搭建能力 三、低代码其他能力 四、写在最后 随着互联网和信息技术的快速发展&#xff0c;各行各业都在积极拥抱数字化转型。在这个过程中&#xff0c;软件开发成为企业实现数字化转型的关键环节。然而&#xff0c;传统的软件开发…

C语言 每日一题 PTA 11.8 day14

1.矩阵A乘以B 给定两个矩阵A和B&#xff0c;要求你计算它们的乘积矩阵AB。需要注意的是&#xff0c;只有规模匹配的矩阵才可以相乘。 即若A有Ra​行、Ca列&#xff0c;B有Rb行、Cb列&#xff0c;则只有Ca与Rb​相等时&#xff0c;两个矩阵才能相乘。 输入格式&#xff1a; 输入…

【Java】IntelliJ IDEA使用JDBC连接MySQL数据库并写入数据

目录 0 准备工作1 创建Java项目2 添加JDBC 驱动程序3 创建数据库连接配置文件4 创建一个 Java 类来连接和操作数据库5 运行应用程序 在 IntelliJ IDEA 中连接 MySQL 数据库并将数据存储在数据表中&#xff0c;使用 Java 和 JDBC&#xff08;Java Database Connectivity&#xf…

Android拖放startDragAndDrop拖拽Glide加载堆叠圆角图,Kotlin(5)

Android拖放startDragAndDrop拖拽Glide加载堆叠圆角图&#xff0c;Kotlin&#xff08;5&#xff09; import android.content.ClipData import android.graphics.Canvas import android.graphics.Point import android.os.Bundle import android.util.Log import android.view.…

Echarts柱状体实现滚动条动态滚动

当我们柱状图中X轴数据太多的时候&#xff0c;会自动把柱形的宽度挤的很细&#xff0c;带来的交互非常不好&#xff0c;因此就有一个属性来解决&#xff1a;dataZoom 第一种简易的版本&#xff0c;横向滚动。 dataZoom: {show: true, // 为true 滚动条出现realtime: true, // 实…

RabbitMQ集群配置以及负载均衡配置

RabbitMQ集群配置以及负载均衡配置 环境配置集群配置安装rabbitmq启动rabbitmq开启远程登录添加用户并且授权用户添加数据存放目录和日志存放目录查看端口拷⻉erlang.cookie将mq-2、mq-3作为内存节点加⼊mq-1节点集群中查看集群状态添加一个新的队列 RabbitMq负载均衡配置-HAPr…

【ChatGLM2-6B】小白入门及Docker下部署

【ChatGLM2-6B】小白入门及Docker下部署 一、简介1、ChatGLM2是什么2、组成部分3、相关地址 二、基于Docker安装部署1、前提2、CentOS7安装NVIDIA显卡驱动1&#xff09;查看服务器版本及显卡信息2&#xff09;相关依赖安装3&#xff09;显卡驱动安装 2、 CentOS7安装NVIDIA-Doc…

自主开发刷题应用网站H5源码(无需后端无需数据库)

该应用使用JSON作为题库的存储方式&#xff0c;层次清晰、结构简单易懂。 配套的word模板和模板到JSON转换工具可供使用&#xff0c;方便将题库从word格式转换为JSON格式。 四种刷题模式包括顺序刷题、乱序刷题、错题模式和背题模式&#xff0c;可以根据自己的需求选择适合的模…

计网----累积应答,TCP的流量控制--滑动窗口,粘包问题,心跳机制,Nagle算法,拥塞控制,TCP协议总结,UDP和TCP对比,中介者模式

计网----累积应答&#xff0c;TCP的流量控制–滑动窗口&#xff0c;粘包问题&#xff0c;心跳机制&#xff0c;Nagle算法&#xff0c;拥塞控制&#xff0c;TCP协议总结&#xff0c;UDP和TCP对比&#xff0c;中介者模式 一.累积应答 1.什么是累计应答 每次发一些包&#xff0…

Leetcode刷题详解—— 组合总和

1. 题目链接&#xff1a;39. 组合总和 2. 题目描述&#xff1a; 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些…