Vue3源码梳理:运行时的设计方案概况

关于运行时和demo简单示例

  • 运行时,简单理解,就是把vnode渲染到页面中

    <div id='app'></div>
    <script>const { render, h } = Vueconst vnode = h('div', {class: 'test',}, 'hello render')const container = document.querySelector('#app')render(vnode, container)
    </script>
    
  • 整个runtime包含两个环节

    • 1.利用h函数生成vnode
    • 2.利用render函数把vnode渲染到指定位置
  • 所以,我们的目标是

    • 理解vnode作用,为何要创建vnode
    • 创建vnode参数是干嘛的,为何要传递这些参数
  • 在理解这些之前, 我们需要了解

    • HTML DOM 节点树 与 虚拟 DOM 树
    • 这两者的区别

HTML DOM 节点树 与 虚拟 DOM 树

1 )两个概念

  • html dom 节点树
  • 虚拟 dom 树
<div><h1> hello h1</h1><!-- 哈哈 -->hello div
</div>
  • 浏览器会把它们通过一个dom树来表示
  • dom树的解释:https://zh.javascript.info/dom-nodes
  • 上述dom树的示例包含
    • 标签点击、注释节点、文本节点

2 )关于虚拟DOM

  • 官方关于虚拟dom的解释:https://cn.vuejs.org/guide/extras/rendering-mechanism.html#virtual-dom
    • 虚拟 DOM (Virtual DOM,简称 VDOM) 是一种编程概念
    • 意为将目标所需的 UI 通过数据结构“虚拟”地表示出来
    • 保存在内存中,然后将真实的 DOM 与之保持同步
    • 这个概念是由 React 率先开拓,随后在许多不同的框架中都有不同的实现,当然也包括 Vue。
  • 虚拟dom是一种理念,期望通过js对象描述一个div节点
  • 所以说,与其说虚拟 DOM 是一种具体的技术,不如说是一种模式,所以并没有一个标准的实现

3 ) 区别示例

拿文本节点来说

html dom 节点树表示

<div>text</div>

虚拟dom表示

const vnode = {type: 'div',children: 'text'
}

总结

  • 在运行时 runtime ,渲染器 renderer 会遍历整个虚拟dom树,并据此结构构建真实的dom树
  • 这个过程我们可以把它叫做挂载 mount
  • 在这个 vnode 对象发生变化时候,我们会对比 旧的 VNode 和 新的 VNode 之间的区别
  • 找出它们之间的区别,并应用这其中的变化到真实的dom上,这个过程叫做更新 patch

关于挂载和更新

简化版的demo

<div id='app'></div>
<script>// <div>hello render</div>const vnode = {type: 'div',children: 'hello render'}const vnode2 = {type: 'div',children: 'hello patch'}function render(oldVNode, newVNode, container) {// 第一次属于挂载,old不存在if(!oldVNode) {mount(newVNode, container)} else {patch(oldVNode, newVNode, container)}}// 挂载方法function mount(vnode, container) {const ele = document.createElement(vnode.type) // 1. 创建当前节点ele.innerText = vnode.children // 2.插入具体节点container.appendChild(ele) // 3. 将创建的节点存放到容器中} // 卸载操作function unmount(container) {container.innerHTML = ''}// 更新操作function patch(oldVNode, newVNode, container) {// 1. 卸载unmount(container)// 2. 重新渲染const ele = document.createElement(newVNode.type)ele.innerText = newVNode.childrencontainer.appendChild(ele)}// 初始化时去挂载render(null, vnode, document.querySelector('#app'))// 延迟两秒进行更新setTimeout(() => {render(vnode, vnode2, document.querySelector('#app'))}, 2000)
</script>
  • 以上是挂载、更新的逻辑,是一个精简版的更新操作
  • vue本质上也是这类操作(删除旧节点,挂载新节点),但是性能更优,实现更复杂

h函数 和 render函数

在vue中的vnode对象实际上属性很多,我们精简一下

{// 是否是一个vnode对象"__v_isVNode": true,// 当前节点类型"type": "div",// 当前节点的属性"props": {"class": "test"}// 它的子节点"children": "hello render"
}
  • h函数本质上就是一个生成vnode的函数
  • https://cn.vuejs.org/api/render-function.html#h

官方示例

import { h } from 'vue'// 除了 type 外,其他参数都是可选的
h('div')
h('div', { id: 'foo' })// attribute 和 property 都可以用于 prop
// Vue 会自动选择正确的方式来分配它
h('div', { class: 'bar', innerHTML: 'hello' })// class 与 style 可以像在模板中一样
// 用数组或对象的形式书写
h('div', { class: [foo, { bar }], style: { color: 'red' } })// 事件监听器应以 onXxx 的形式书写
h('div', { onClick: () => {} })// children 可以是一个字符串
h('div', { id: 'foo' }, 'hello')// 没有 prop 时可以省略不写
h('div', 'hello')
h('div', [h('span', 'hello')])// children 数组可以同时包含 vnode 和字符串
h('div', ['hello', h('span', 'hello')])
  • 官方文档上提供了各种各样的使用方式
  • 注意,除了 type 外,其他参数都是可选的
  • h 函数最多可接收三个参数
    • type: string | Component: 既可以是字符串(原生标记),也可以是一个Vue组件的定义
    • props?: object | null: 要传递的 prop
    • children?: Children | Slot | Slots: 子节点

2 ) render 函数

  • https://cn.vuejs.org/api/options-rendering.html#render

    render(vnode, container)
    
  • vnode 虚拟dom树

  • container: 承载的容器,真实节点的渲染节点位置

  • 通过render函数,我们可以通过编程形式来把虚拟dom转换成真实dom挂载到指定的容器盒子上

核心设计原则


1 ) 概述

  • vue源码中包含两块
    • runtime-core
    • runtime-dom
  • vue为什么要这么划分
    • 为什么不像是reactivity,都组织到一起
  • vue挂载和更新的逻辑处理是什么

2 ) vue为何分开设计

  • runtime-core 是运行时核心代码

    • 只放核心逻辑,不会放置宿主环境下的相关操作
    • 当当前的vue需要在浏览器端运行时,它就可以把操作dom的一些逻辑作为参数传递到render里面
    • 比如在 baseCreateRenderer 方法中的options,里面可以解构出很多 API
    • 这些 API 都是宿主环境传过来的函数
    • 假如当前 Vue 需要在浏览器上渲染,就把自己的一些 API 传递过来,类似于接口对接的方式,满足不同宿主平台的调用
    • 以此来满足不同平台的不同挂载渲染场景
  • runtime-dom 是浏览器渲染的核心逻辑,多是一个浏览器相关基本操作

    • 这里的很多API都会被作为参数,传递到 runtime-core 包中使用
    • 实现了 渲染 和 宿主平台 两者的解耦
    • 这里宿主平台可以是浏览器,可以是类浏览器环境
    • 一般基于vue渲染都是spa,服务端渲染 ssr
    • 除了这些渲染情况,还有weex, uniapp等都会用到vue的渲染服务
  • 所以

    • 分包的原因是:
      • 针对不同的宿主环境使用不同的API
  • 挂载和更新的逻辑处理

    • baseCreateRenderer 最终返回了一个对象 {render, hydrate, createApp},这个对象里有 render 函数

    • render函数有三个参数: vnode, container, isSVG 这第三个参数不用管

      const render: RootRenderFunction = (vnode, container, isSVG) => {// 不存在vnodeif (vnode == null) {if (container._vnode) {unmount(container._vnode, null, null, true)}} else {// 存在vnode更新patch(container._vnode || null, vnode, container, null, null, null, isSVG)}flushPostFlushCbs()container._vnode = vnode
      }
      
    • 进入patch函数

      const patch: PatchFn = (n1,n2,container,anchor = null,parentComponent = null,parentSuspense = null,isSVG = false,slotScopeIds = null,optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
      ) => {if (n1 === n2) {return}// patching & not same type, unmount old treeif (n1 && !isSameVNodeType(n1, n2)) {anchor = getNextHostNode(n1)unmount(n1, parentComponent, parentSuspense, true)n1 = null}if (n2.patchFlag === PatchFlags.BAIL) {optimized = falsen2.dynamicChildren = null}const { type, ref, shapeFlag } = n2switch (type) {case Text:processText(n1, n2, container, anchor)breakcase Comment:processCommentNode(n1, n2, container, anchor)breakcase Static:if (n1 == null) {mountStaticNode(n2, container, anchor, isSVG)} else if (__DEV__) {patchStaticNode(n1, n2, container, isSVG)}breakcase Fragment:processFragment(n1,n2,container,anchor,parentComponent,parentSuspense,isSVG,slotScopeIds,optimized)breakdefault:if (shapeFlag & ShapeFlags.ELEMENT) {processElement(n1,n2,container,anchor,parentComponent,parentSuspense,isSVG,slotScopeIds,optimized)} else if (shapeFlag & ShapeFlags.COMPONENT) {processComponent(n1,n2,container,anchor,parentComponent,parentSuspense,isSVG,slotScopeIds,optimized)} else if (shapeFlag & ShapeFlags.TELEPORT) {;(type as typeof TeleportImpl).process(n1 as TeleportVNode,n2 as TeleportVNode,container,anchor,parentComponent,parentSuspense,isSVG,slotScopeIds,optimized,internals)} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {;(type as typeof SuspenseImpl).process(n1,n2,container,anchor,parentComponent,parentSuspense,isSVG,slotScopeIds,optimized,internals)} else if (__DEV__) {warn('Invalid VNode type:', type, `(${typeof type})`)}}// set refif (ref != null && parentComponent) {setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)}
      }
      
    • 里面有一个switch,根据当前vnode的type来划分vnode的类型进行各自的处理

    • 挂载的操作,本质上都是依赖patch函数来执行的,内部根据type来匹配各类挂载流程

    • 所以,整个挂载的操作,本质上是依赖于patch函数来执行的,内部基于type来执行不同类型节点的挂载

    • 整个render的大致逻辑如下

      • baseCreateRenderer 这个函数
        • 包含核心的render方法
        • render方法的渲染会在vnode存在的时候使用patch函数
        • patch函数会根据当前节点的vnode类型来选择不用的节点挂载
        • 而每一种类型的挂载节点都类似处理:
          • 旧节点不存在时,进行挂载;
          • 旧节点存在进行更新

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

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

相关文章

UDP分片与丢包,UDP真的比TCP高效吗?

一、UDP 报文格式 每个 UDP 报文分为 UDP 报头和 UDP 数据区两部分。报头由 4 个 16 位长&#xff08;2 字节&#xff09;字段组成&#xff0c;分别说明该报文的源端口、目的端口、报文长度和校验值。 UDP 报文格式如图所示。 UDP 报文中每个字段的含义如下&#xff1a; 源端…

Pytest自动化测试用例中的断言详解

前言 测试的主要工作目标就是验证实际结果与预期结果是否一致&#xff1b;在接口自动化测试中&#xff0c;通过断言来实现这一目标。Pytest中断言是通过assert语句实现的&#xff08;pytest对Python原生的assert语句进行了优化&#xff09;&#xff0c;确定实际情况是否与预期一…

通过“待办事项列表项目”快速学习Pyqt5的一些特性

Pyqt5相关文章: 快速掌握Pyqt5的三种主窗口 快速掌握Pyqt5的2种弹簧 快速掌握Pyqt5的5种布局 快速弄懂Pyqt5的5种项目视图&#xff08;Item View&#xff09; 快速弄懂Pyqt5的4种项目部件&#xff08;Item Widget&#xff09; 快速掌握Pyqt5的6种按钮 快速掌握Pyqt5的10种容器&…

Apache Flume(3):数据持久化

1 使用组件 File Channel 2 属性设置 属性名默认值说明type-filecheckpointDir~/.flume/file-channel/checkpoint检查点文件存放路径dataDirs~/.flume/file-channel/data日志存储路径&#xff0c;多个路径使用逗号分隔. 使用不同的磁盘上的多个路径能提高file channel的性能 …

【.Net 6.0--通用帮助类--ConvertHelper】

前言 类型转换帮助类&#xff0c;包含下表中的方法&#xff1a; 方法名方法解释ObjToIntobject转intObjToMoneyobject转doubleObjToStringobject转stringObjToDecimalobject转decimalObjToDateobject转datetimeObjToDateSplitYMDobject转datetime&#xff08;yyyy-MM-dd&…

前端已死?尊嘟假嘟?

随着人工智能和低代码的崛起&#xff0c;“前端已死”的声音逐渐兴起。前端已死&#xff1f;尊嘟假嘟&#xff1f; 一、为什么会出现“前端已死”的言论 我认为有以下三方面&#xff1a; 低代码平台的兴起&#xff1a;低代码平台使得非技术背景的人也能够轻松地创建和部署应用…

SpringIOC之@Primary

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

nodejs微信小程序+python+PHP技术下的音乐推送系统-计算机毕业设计推荐

音乐推送系统采取面对对象的开发模式进行软件的开发和硬体的架设&#xff0c;能很好的满足实际使用的需求&#xff0c;完善了对应的软体架设以及程序编码的工作&#xff0c;采取MySQL作为后台数据的主要存储单元&#xff0c;  本文设计了一款音乐推送系统&#xff0c;系统为人…

Linux系统用户账号的管理

摘要&#xff1a;本文将详细介绍Linux系统中用户账号的创建、删除、修改以及权限设置等操作&#xff0c;帮助读者更好地理解Linux用户账号的管理。 一、引言 Linux操作系统是一个多用户的操作系统&#xff0c;每个用户都有一个唯一的用户名和密码。通过合理的管理用户账号&am…

vue中2种取值的方式

1.url是这种方式的&#xff1a;http://localhost:3000/user/1 取得参数的方式为&#xff1a;this.$route.params.id 2.url为get方式用&#xff1f;拼接参数的&#xff1a;http://localhost:3000/user?phone131121123&companyId2ahttp://localhost:3000/ 取得参数值的方式…

uniapp 微信小程序 封装axios 包含请求拦截、响应拦截、无感刷新令牌功能

前言&#xff1a; 1、为什么不适用uniapp自带的请求功能&#xff1f; 答&#xff1a;uniapp自带的请求功能&#xff0c;再刷新了令牌后&#xff0c;重新请求返回的数据无法返回给发起请求的方法。也就是说&#xff0c;刷新令牌后重新发起的请求和第一次发起请求的方法是割裂的。…

Django去访问web api接口Object of type Session is not JSON serializable

解决方案&#xff1a;settings.py中加入 &#xff1a;SESSION_SERIALIZER django.contrib.sessions.serializers.PickleSerializer 事由&#xff1a;Django去访问一个web api接口&#xff0c;两次连接之间需要通过Session()保持身份验证。 def sendCode(request): mobile jso…

freeRTOS使用

创建第一个FreeRTOS程序 1、官网源码下载 &#xff08;1&#xff09;进入FreeRTOS官网FreeRTOS professional services for application and RTOS development and consulting. FreeRTOS is an Open Source Code RTOS &#xff08;2&#xff09;点击下载FreeRTOS 2、处理目录 &…

基于多智能体系统一致性算法的电力系统分布式经济调度策略MATLAB程序

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 参考文献&#xff1a; 主要内容&#xff1a; 应用多智能体系统中的一致性算法&#xff0c;以发电机组的增量成本和柔性负荷的增量效益作为一致性变量&#xff0c;设计一种用于电力系统经济调度的算法&#x…

openwrt中taiscale自动安装脚本详解

openwrt中taiscale自动安装脚本详解 一、代码仓库地址 https://github.com/adyanth/openwrt-tailscale-enabler 二、代码仓库中脚本文件详解 主要包含三个脚本分别是etc/init.d/tailscale、usr/bin/tailscale、usr/bin/tailscaled &#xff0c;接下来逐个分析一下脚本中的具…

shell提示符换行

shell 默认的提示符形如 [<用户名><主机名> <路径>]$&#xff0c;其格式串定义形如 PS1"[\u\h \w]\n\$ "&#xff0c;可能在 ~/.bashrc&#xff0c;也可能在 /etc/bashrc。 有时路径太长&#xff0c;但想保留方便复制&#xff0c;于是想将提示符换…

3.1【窗口】窗口简介与窗口组

一,窗口简介 Windows用于显示内容,并将不同生成的内容组合在一起。每个不同的呈现器都可以在同一个进程中,也可以在另一个或多个进程中。 Screen中的窗口概念与你在传统窗口系统中可能习惯的略有不同。在Screen中,当内容来自不同来源时,应用程序被分成几个窗口,当应用程…

Linux查看进程PID以及杀掉进程的方法

目录 参考链接 前言 查看进程PID PS命令 ps -le命令 查找父进程 杀死进程 参考链接 【Linux 】 ps命令详解&#xff0c;查看进程pid_linux查看pid 对应的程序-CSDN博客 Linux查看进程PID的方法&#xff08;linux查进程的pid&#xff09;附带自动kill 掉_linux查看pid 对…

Fanuc-Focas_库函数

Fanuc-Focas库函数是一种在Fanuc系统中广泛使用的库函数&#xff0c;它包含了多种与机床控制器相关的功能和接口。以下是针对这些方面的简单描述和相关库函数。 系统函数 initializeSystem(): 初始化系统&#xff0c;进行必要的设置和启动。systemStatus(): 获取系统状态&#…

Python基础入门:语法与数据类型

Python基础入门&#xff1a;语法与数据类型 一、引言 Python是一种简单易学、功能强大的编程语言&#xff0c;广泛应用于数据分析、机器学习、Web开发等领域。本文将介绍Python的基础语法和数据类型&#xff0c;帮助初学者快速入门。 二、Python基础语法 缩进 Python中的缩…