vue3学习源码笔记(小白入门系列)------ 组件是如何渲染成dom挂载到指定位置的?

文章目录

    • os
    • 准备
      • 组件如何被挂载到页面上
        • 第一步 createApp 做了哪些工作?
          • ensureRenderer
          • baseCreateRenderer
          • createAppAPI
          • mount
          • render
          • patch
          • processComponent
          • processElement
      • 总结


os

学习一下vue3 源码,顺便记录分享下

使用vitest 插件调试源码 辅助阅读

准备

去 github 下载 vue3源码 最新仓库名 为 core-main 使用 版本 为3.3.4
在这里插入图片描述

安装好依赖 
npm i pnpm -g pnpm install

vscode 准备两个插件 方便代码调试
在这里插入图片描述

在这里插入图片描述
安装后会出现调试icon 未生效 可以重启vscode 。

在这里插入图片描述
代码打上 断点, 开启debug 调试

在这里插入图片描述

1 跳到下一个方法体
2 逐步执行
3 回退到上一步
4 重新执行
最后一个按钮就是 结束执行

组件如何被挂载到页面上

createApp(App).mount('#app')

第一步 createApp 做了哪些工作?

先看下入参和出参

export type CreateAppFunction<HostElement> = (rootComponent: Component,rootProps?: Data | null
) => App<HostElement> 入参: rootComponent 需要渲染的组件 App 也就是我们编写的 App.vue 文件rootProps 传入根实例 的 props 最后会被 挂在 app _props 上
出参 : 返回app 实例对象
// packages/runtime-dom/src/index.ts
export const createApp = ((...args) => {// 调用 ensureRender 生成render 对象 const render = ensureRenderer()// 再调用 render中 createApp 方法 来生成 app实例 const app = render.createApp(...args)···· 下面先省略return app
})
ensureRenderer
// packages/runtime-dom/src/renderer.ts
// 实际调用的是 createRenderer
function ensureRenderer() {
/*
大致意思是 判断renderer实例是否存在,有就直接返回 
没有执行 createRender 方法并 赋值 renderer 再返回  
这里返回的 renderer 对象,可以认为是一个跨平台的渲染器对象,
针对不同的平台,会创建出不同的 renderer 对象,
上述是创建浏览器环境的 renderer 对象,对于服务端渲染的场景,
则会创建 server render 的 renderer*/return (renderer ||(renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions)))
}// 实际调用 baseCreateRenderer
function createRenderer<HostNode = RendererNode,HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {return baseCreateRenderer<HostNode, HostElement>(options)
}
baseCreateRenderer

有两种模式 hydration 模式是 服务端渲染的 我们只考虑 no-hydration 浏览器渲染
在这里插入图片描述
no-hydration 下
入参: options 初始化 渲染的参数

在这里插入图片描述

出参 :render  export interface Renderer<HostElement = RendererElement> {render: RootRenderFunction<HostElement>createApp: CreateAppFunction<HostElement>
}

具体伪代码 实现
// packages/runtime-core/src/renderer.ts
export function createRenderer(options) {// ...// 这里不介绍 hydrate 模式return {render,hydrate, // no-hydration 为空createApp: createAppAPI(render, hydrate),}
}

在这里插入图片描述

createAppAPI
// packages/runtime-core/src/apiCreateApp.ts
function createAppAPI(render, hydrate) {// createApp createApp 方法接收的两个参数:根组件的对象和 propreturn function createApp(rootComponent, rootProps = null) {// 。。。 省略const app = {// ... 省略很多不需要在这里介绍的属性_component: rootComponent,_props: rootProps,mount(rootContainer, isHydrate, isSVG) {// ...}}return app}
}

Vue 3 初始化根组件的核心方法,也就是入口文件 createApp 真正执行的内容就是这里的 createAppAPI 函数中的 createApp 函数,该函数接收了 组件作为根组件 rootComponent,返回了一个包含 mount 方法的 app 对象,再看看 mount 具体实现

mount
// packages/runtime-core/src/apiCreateApp.ts
mount(rootContainer, isHydrate, isSVG) {if (!isMounted) {// ... 省略部分不重要的代码// 1. 创建根组件的 vnodeconst vnode = createVNode(rootComponent,rootProps)// 2. 渲染根组件  这里render方法 其实是baseCreateRenderer // 返回的render对象带的 render方法 // 作用就是 将 vnode 渲染成真实domrender(vnode, rootContainer, isSVG)isMounted = true}
}
render
// packages/runtime-core/src/renderer.ts
const render: RootRenderFunction = (vnode, container, isSVG) => {// console.log('render-----');//第一个 入参 没传 代表 需要卸载 if (vnode == null) {if (container._vnode) {unmount(container._vnode, null, null, true)}} else {// 否则走 挂载 或更新 操作patch(container._vnode || null, vnode, container, null, null, null, isSVG)}flushPreFlushCbs()flushPostFlushCbs()container._vnode = vnode}
// patch  所有vnode diff 比对 更新 转化新dom 操作全在里面
patch
const patch: PatchFn = (n1, //  需要 对比的 旧 vnoden2, // 新生成的 vnode container, // 最后生成的元素 需要挂载到的 目标组件元素anchor = null, // 挂载的参考元素;parentComponent = null, // 父组件parentSuspense = null,isSVG = false,slotScopeIds = null,optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren) => {//n1 n2 完全一致 就 直接返回 不做更新 或 挂载if (n1 === n2) {return}// patching & not same type, unmount old tree  新旧 vnode  类型 不一样 直接 卸载 n1 if (n1 && !isSameVNodeType(n1, n2)) {anchor = getNextHostNode(n1)unmount(n1, parentComponent, parentSuspense, true)n1 = null}if (n2.patchFlag === PatchFlags.BAIL) {optimized = falsen2.dynamicChildren = null}// shapeFlag 判断vnode 实例是什么类型 有的是元素类型 函数类型 组件类型等const { type, ref, shapeFlag } = n2switch (type) {//文本节点case Text:processText(n1, n2, container, anchor)break// 注释节点case 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:// 处理 template 的虚拟标签processFragment(n1,n2,container,anchor,parentComponent,parentSuspense,isSVG,slotScopeIds,optimized)breakdefault:// 其它类型//ShapeFlags 是一个二进制左移操作符生成的对象if (shapeFlag & ShapeFlags.ELEMENT) {// 这里走的是 组件内部元素普通dom的比对更新挂载逻辑processElement(n1,n2,container,anchor,parentComponent,parentSuspense,isSVG,slotScopeIds,optimized)} else if (shapeFlag & ShapeFlags.COMPONENT) {// 这里是 组件对比 component 逻辑 processComponent(n1,n2,container,anchor,parentComponent,parentSuspense,isSVG,slotScopeIds,optimized)} 。。。 // 其它省略// set refif (ref != null && parentComponent) {/*通过 ref 参数获取组件的引用对象。通过 n1 参数获取前一个 VNode 的引用对象(如果存在)。通过 n2 参数获取当前 VNode 的引用对象(如果存在)。如果前一个 VNode 的引用对象存在(即 n1.ref 存在),则将其置为 null,解除对前         一个组件引用的绑定。如果当前 VNode 的引用对象存在(即 n2.ref 存在),则将其绑定到组件的引用上。如果当前 VNode 不存在(即 !n2),则将组件的引用对象置为 null*/setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)}}

初始化挂载 会进入到 processComponent方法

processComponent
// packages/runtime-core/src/renderer.ts
function processComponent(n1, n2, container, parentComponent) {// 如果 n1 没有值的话,那么就是 mountif (!n1) {// 初始化 componentmountComponent(n2, container, parentComponent);} else {updateComponent(n1, n2, container);}
}// packages/runtime-core/src/renderer.ts
function mountComponent(initialVNode, container, parentComponent) {// 1. 先创建一个 component instance const instance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));// 2. 初始化 instance 上的 props, slots, 执行组件的 setup 函数...setupComponent(instance);// 3. 设置并运行带副作用的渲染函数setupRenderEffect(instance, initialVNode, container);
}// packages/runtime-core/src/component.ts
function createComponentInstance(vnode: VNode,parent: ComponentInternalInstance | null,suspense: SuspenseBoundary | null
) {const type = vnode.type as ConcreteComponent// inherit parent app context - or - if root, adopt from root vnodeconst appContext =(parent ? parent.appContext : vnode.appContext) || emptyAppContextconst instance: ComponentInternalInstance = {uid: uid++,vnode,type,parent,appContext,root: null!, // to be immediately setnext: null,subTree: null!, // will be set synchronously right after creationeffect: null!,update: null!, // will be set synchronously right after creationscope: new EffectScope(true /* detached */),render: null,proxy: null,//。。。 省略 属性}if (__DEV__) {instance.ctx = createDevRenderContext(instance)} else {instance.ctx = { _: instance }}instance.root = parent ? parent.root : instanceinstance.emit = emit.bind(null, instance)// apply custom element special handlingif (vnode.ce) {vnode.ce(instance)}return instance
}// packages/runtime-core/src/component.ts
export function setupComponent(instance) {// 1. 处理 props// 取出存在 vnode 里面的 props const { props, children } = instance.vnode;initProps(instance, props);// 2. 处理 slotsinitSlots(instance, children);// 3. 调用 setup 并处理 setupResultsetupStatefulComponent(instance);
}// packages/runtime-core/src/renderer.ts
/*
componentUpdateFn 这个函数,
核心是调用了 renderComponentRoot 来生成 subTree,
然后再把 subTree 挂载到 container 中
*/
const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {function componentUpdateFn() {if (!instance.isMounted) {// 渲染子树的 vnodeconst subTree = (instance.subTree = renderComponentRoot(instance))// 挂载子树 vnode 到 container 中 // 会重新进入 patch 方法 会走到 processElement 方法中patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)// 把渲染生成的子树根 DOM 节点存储到 el 属性上initialVNode.el = subTree.elinstance.isMounted = true}else {// 更新相关,后面介绍}}// 创建副作用渲染函数instance.update = effect(componentUpdateFn, prodEffectOptions)
}/*
返回 vnode
*/
function renderComponentRoot(instance: ComponentInternalInstance
): VNode {const {type: Component,vnode,proxy,withProxy,props,propsOptions: [propsOptions],slots,attrs,emit,render,renderCache,data,setupState,ctx,inheritAttrs} = instanceconst proxyToUse = withProxy || proxy// 省略一部分逻辑判断 normalizeVNode 
/*render 方法 其实是调用instance.render 方法 就是在 初始化instance 方法 中 将 template 模版 编译成 render 方法 用于 生成  vnode
*/result = normalizeVNode(render!.call(proxyToUse,proxyToUse!,renderCache,props,setupState,data,ctx))return result 
}
processElement
// packages/runtime-core/src/renderer.ts
function processElement(n1, n2, container, anchor, parentComponent) {if (!n1) {// 挂载元素节点mountElement(n2, container, anchor);} else {// 更新元素节点updateElement(n1, n2, container, anchor, parentComponent);}
}// packages/runtime-core/src/renderer.ts
const mountElement = (vnode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {let elconst { type, props, shapeFlag, transition, patchFlag, dirs } = vnode// ...// 根据 vnode 创建 DOM 节点el = vnode.el = hostCreateElement(vnode.type, isSVG, props && props.is)if (props) {// 处理 props 属性for (const key in props) {if (!isReservedProp(key)) {hostPatchProp(el, key, null, props[key], isSVG)}}}// 文本节点处理if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {hostSetElementText(el, vnode.children)} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {// 如果节点是个数据类型,则递归子节点mountChildren(vnode.children, el)}// 把创建好的 el 元素挂载到容器中hostInsert(el, container, anchor)
}

总结

以上就完成了 组件初始化工作。下面画了 几个流程图来辅助理解 。最好阅读的时候自己 也可以画下

安利一个好用的vscode流程图插件
在这里插入图片描述

请添加图片描述

下一篇:准备写 数据代理这块

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

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

相关文章

layui框架学习(40:数据表格_主要事件)

Layui数据表格模块主要通过各类事件响应工具栏操作、单元格编辑或点击等交互操作&#xff0c;本文学习table数据表格模块中的主要事件及处理方式。   头部工具栏事件。通过代码“table.on(‘toolbar(test)’, function(obj))”获取lay-filter属性为test的数据表格的头部工具栏…

javeee eclipse项目导入idea中

步骤一 复制项目到idea工作空间 步骤二 在idea中导入项目 步骤三 配置classes目录 步骤四 配置lib目录 步骤五 添加tomcat依赖 步骤六 添加artifacts 步骤七 部署到tomcat

Mybatis+MybatisPlus拦截器实战之数据的加解密和脱敏

文章目录 一、前言二、拦截器简介三、代码目录结构简介四、核心代码讲解4.1 application.yml文件4.2 自定义注解4.2.1 SensitiveEntity4.2.2 SensitiveData4.2.3 MaskedEntity4.2.4 MaskedField4.2.5 MaskedMethod 4.3 Mybatis-Plus 拦截器数据自动加密4.4 Mybatis 打印完整sql…

添加了.gitignore 文件,git status 的时候还是显示修改文件

1. 用IAR 软件编译STM32 工程&#xff0c;IAR 会生成很多中间文件&#xff0c;这些文件是不需要加入到git 版本管理里面的 2. .gitignore 文件位置需要放对应目录才会起作用&#xff0c;递归起作用的 3. 如果 .gitignore文件中指定的文件或目录仍然显示在git status的输出中&a…

万字长文:Stable Diffusion 保姆级教程

万字长文&#xff1a;Stable Diffusion 保姆级教程 2022年绝对是人工智能爆发的元年&#xff0c;前有 stability.ai 开源 Stable Diffusion 模型&#xff0c;后有 Open AI 发布 ChatGPT&#xff0c;二者都是里程碑式的节点事件&#xff0c;其重要性不亚于当年苹果发布iPhone&a…

Java接口(interface)

接口&#xff08;interface&#xff09;明确了描述类被授权了哪些能力&#xff0c;但不会指定具体的方式。实现类&#xff08;implement&#xff09;一个或多个接口。–>使类完成了实现&#xff0c;是一种对于行为规范的准则的抽象。 个体的方法可以在子类中自写展现&#…

sql server 、mysql CTE 公用表表达式

sql server 详细 mysql CTE CTE 是一个命名的临时结果集&#xff0c;作用范围是当前语句。CTE可以理解成一个可以复用的子查询&#xff0c;当然跟子查询还是有点区别的&#xff0c;CTE可以引用其他CTE&#xff0c;但子查询不能引用其它子查询。所以&#xff0c;开发中建议…

2023企业网盘产品排行榜揭晓:选择最适合你的企业网盘工具

企业网盘产品已成为企业文件管理协作的主要选择之一&#xff0c;无论是在文件管理方面&#xff0c;还是团队协作上&#xff0c;企业网盘都表现优秀。为了帮助企业选到心怡的企业网盘产品&#xff0c;我们综合了不同的产品测评网站意见&#xff0c;整理了2023企业网盘产品排行榜…

Python高光谱遥感数据处理与高光谱遥感机器学习方法应用

本文提供一套基于Python编程工具的高光谱数据处理方法和应用案例。 本文涵盖高光谱遥感的基础、方法和实践。基础篇以学员为中心&#xff0c;用通俗易懂的语言解释高光谱的基本概念和理论&#xff0c;旨在帮助学员深入理解科学原理。方法篇结合Python编程工具&#xff0c;专注…

python下timer定时器常用的两种实现方法

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 话不多说&#xff0c;直接开搞&#xff0c;如果有什么疑惑/资料需要的可以点击文章末尾名片领取源码 方法一&#xff0c;使用线程中现成的&#xff1a; 这种一般比较常用&#xff0c;特别是在线程中的使用方法&#xff0c;下…

K8S如何部署ZooKeeper以及如何进行ZooKeeper的平滑替换

前言 在之前的章节中&#xff0c;我们已经成功地将Dubbo项目迁移到了云环境。在这个过程中&#xff0c;我们选择了单机ZooKeeper作为注册中心。接下来&#xff0c;我们将探讨如何将单机ZooKeeper部署到云端&#xff0c;以及在上云过程中可能遇到的问题及解决方案。 ZooKeeper…

Docker安装ES+kibana8.9.1

参考&#xff1a;基于Docker安装Elasticsearch【保姆级教程、内含图解】_docker elasticsearch_Acloasia的博客-CSDN博客 创建网络 docker network create es-net 基于Docker安装Elasticsearch 拉取镜像 docker pull elasticsearch:8.9.1 挂载文件 mkdir -p /usr/local/e…

Excel 分组排名

分组排名 公式&#xff1a;SUMPRODUCT((A:AA2)*(C:C>C2)) 1 降序&#xff1a;> 改为 < ⚠️注意1&#xff1a;此处空值参与排名&#xff1b;不参与排名则公式改为&#xff1a;IF(C2“”,“”,SUMPRODUCT((A:AA2)*(C:C>C2)) 1) ⚠️注意2&#xff1a;相同值的项…

2023年清洁能源与智能电网国际会议(CCESG 2023)

会议简介 Brief Introduction 2023年清洁能源与智能电网国际会议(CCESG 2023) 会议时间&#xff1a;2023年 召开地点&#xff1a;中国南宁 大会官网&#xff1a;CCESG 2023-2023 International Joint Conference on Clean Energy and Smart Grid 由IASED主办&#xff0c; CoreS…

PHP自己的框架session()使用(完善篇六)

1、PHP自己的框架session() 2、session类&#xff08;SessionBase.php&#xff09; <?php class SessionBase {/*** 设置session*/public static function set($name, $data, $expire600){$session_data array();$session_data[data] $data;$session_data[expire] time…

PC上架货源的方式

PC上架货源的方式 第一步建立文档夹 给他发一个snipaste的文件包 1、第一步搜索1688 移动端上架货源的方式 货源上架 弄两个方框&#xff0c;上架货源&#xff0c;先下载软件

数据库三大范式是什么,又为什么要反范式?

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;CSDN领军人物&#xff0c;全栈领域优质创作者✌&#xff0c;CSDN博客专家&#xff0c;阿里云社区专家博主&#xff0c;2023年6月CSDN上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师…

“分布式”与“集群”初学者的技术总结

一、“分布式”与“集群”的解释&#xff1a; 分布式&#xff1a;把一个囊肿的系统分成无数个单独可运行的功能模块 集群&#xff1a; 把相同的项目复制进行多次部署&#xff08;可以是一台服务器多次部署&#xff0c;例如使用8080部署一个&#xff0c;8081部署一个&#xff0c…

使用cgroup工具对服务器某些/全部用户进行计算资源限制

使用cgroup工具对服务器某些/全部用户进行计算资源限制 主要介绍&#xff0c;如何对指定/所有用户进行资源限定&#xff08;这里主要介绍cpu和内存占用限制&#xff09;&#xff0c;防止某些用户大量占用服务器计算资源&#xff0c;影响和挤占他人正常使用服务器。 安装cgrou…

【目标检测】理论篇(2)YOLOv3网络构架及其代码实现

网络构架图&#xff1a; 代码实现&#xff1a; import math from collections import OrderedDictimport torch.nn as nn#---------------------------------------------------------------------# # 残差结构 # 利用一个1x1卷积下降通道数&#xff0c;然后利用一个3x3卷…