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

news2024/11/18 2:44:09

文章目录

    • 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 方法接收的两个参数:根组件的对象和 prop
  return 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. 创建根组件的 vnode
    const vnode = createVNode(
      rootComponent,
      rootProps
    )
    
    // 2. 渲染根组件  这里render方法 其实是baseCreateRenderer 
    // 返回的render对象带的 render方法 
    // 作用就是 将 vnode 渲染成真实dom
    render(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, //  需要 对比的 旧 vnode
    n2, // 新生成的 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 = false
      n2.dynamicChildren = null
    }
    // shapeFlag 判断vnode 实例是什么类型 有的是元素类型 函数类型 组件类型等
    const { type, ref, shapeFlag } = n2
    switch (type) {
       //文本节点
      case Text:
        processText(n1, n2, container, anchor)
        break
       // 注释节点
      case Comment:
        processCommentNode(n1, n2, container, anchor)
        break
      case Static:
        if (n1 == null) {
          mountStaticNode(n2, container, anchor, isSVG)
        } else if (__DEV__) {
          patchStaticNode(n1, n2, container, isSVG)
        }
        break
      case Fragment:
      // 处理 template 的虚拟标签
        processFragment(
          n1,
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          optimized
        )
        break
      default:
        // 其它类型
        //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 ref
    if (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 没有值的话,那么就是 mount
  if (!n1) {
    // 初始化 component
    mountComponent(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 vnode
  const appContext =
    (parent ? parent.appContext : vnode.appContext) || emptyAppContext

  const instance: ComponentInternalInstance = {
    uid: uid++,
    vnode,
    type,
    parent,
    appContext,
    root: null!, // to be immediately set
    next: null,
    subTree: null!, // will be set synchronously right after creation
    effect: null!,
    update: null!, // will be set synchronously right after creation
    scope: new EffectScope(true /* detached */),
    render: null,
    proxy: null,
   //。。。 省略 属性
  }
  if (__DEV__) {
    instance.ctx = createDevRenderContext(instance)
  } else {
    instance.ctx = { _: instance }
  }
  instance.root = parent ? parent.root : instance
  instance.emit = emit.bind(null, instance)

  // apply custom element special handling
  if (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. 处理 slots
  initSlots(instance, children);

  // 3. 调用 setup 并处理 setupResult
  setupStatefulComponent(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) {
      // 渲染子树的 vnode
      const subTree = (instance.subTree = renderComponentRoot(instance))
      // 挂载子树 vnode 到 container 中 
      // 会重新进入 patch 方法 会走到 processElement 方法中
      patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)
      // 把渲染生成的子树根 DOM 节点存储到 el 属性上
      initialVNode.el = subTree.el
      instance.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
  } = instance

  const 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 el
  const { 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.coloradmin.cn/o/928612.html

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

相关文章

0825|C++day5 运算符重载+静态成员+类的基础【Xmind+实例】

一、运算符重载 实例&#xff1a;&#xff08;赋值运算符、自增自减运算符、插入提取运算符&#xff09; #include <iostream>using namespace std;class Person {friend Person & operator(Person &L,const Person &R);friend Person & operator(Perso…

RabbitMQ 集群

clustering 最开始我们介绍了如何安装及运行 RabbitMQ 服务&#xff0c;不过这些是单机版的&#xff0c;无法满足目前真实应用的要求。如果 RabbitMQ 服务器遇到内存崩溃、机器掉电或者主板故障等情况&#xff0c;该怎么办&#xff1f;单台 RabbitMQ服务器可以满足每秒 100…

DDR与PCIe:高性能SoC的双引擎

SoC芯片无处不在&#xff0c;小到家电控制的MCU&#xff0c;大到手机芯片&#xff0c;我们都会接触到。如今大部分芯片设计公司都在开发SoC芯片&#xff0c;一颗SoC芯片可以集成越来越多的功能&#xff0c;俨然它已成为IC设计业界的焦点。 高性能、高速、高带宽的互联和存储的…

SpringIoC基于注解配置

目录 一、Bean注解标记和扫描 (IoC) 二、组件&#xff08;Bean&#xff09;作用域和周期方法注解 三、Bean属性赋值&#xff1a;引用类型自动装配 (DI) 四、Bean属性赋值&#xff1a;基本类型属性赋值 (DI) 一、Bean注解标记和扫描 (IoC) 一、注解方式介绍 1.注解介绍 和…

不同版本.net引用同一个项目

项目文件.csproj文件内容如下&#xff1a; 重点是&#xff1a;不能有其他的 netstandard2;net40;net45;net46;net6 <Project Sdk"Microsoft.NET.Sdk"><PropertyGroup><TargetFrameworks>netstandard2;net40;net45;net46;net6</TargetFrame…

学习pytorch5 常用的transforms

常用的transforms 1. ToTensor()2. Normalize() 1. ToTensor() 2. Normalize() # 1. ToTensor 把PIL图片类型数据或ndarry numpy数据类型转换为tensor类型数据 from cv2 import imread from torchvision import transforms from torch.utils.tensorboard import SummaryWrit…

Java | IDEA中Netty运行多个client的方法

想要运行多个client但出现这种提示&#xff1a; 解决方法 1、打开IDEA&#xff0c;右上角找到下图&#xff0c;并点击 2、勾选

2023.8.26-2023.9.3 周报【3D+GAN+Diffusion基础知识+训练测试】

目录 学习目标 学习内容 学习时间 学习产出 学习目标 1. 3D方向的基础知识 2. 图像生成的基础知识&#xff08;GAN \ Diffusion&#xff09; 3. 训练测试GAN和Diffusion 学习内容 1. 斯坦福cv课程-3D &#xff08;网课含PPT&#xff09; 2. sjtu生成模型课件 3. ge…

截止到目前全国全量在营企业数量有多少?

企业是现代经济社会中最重要的参与主体&#xff0c;它的每一项行为都直接或者间接地关联社会经济总量的变化。 企业数量有什么作用&#xff1f; 谈到企业数量&#xff0c;我们会想到相关行业发展&#xff0c;就业岗位&#xff0c;经济发展等方面的关联。但其实早在13年&#…

限时 180 天,微软为 RHEL 9 和 Ubuntu 22.04 推出 SQL Server 2022 预览评估版

导读近日消息&#xff0c;微软公司今天发布新闻稿&#xff0c;宣布面向 Red Hat Enterprise Linux&#xff08;RHEL&#xff09;9 和 Ubuntu 22.04 两大发行版&#xff0c;以预览模式推出 SQL Server 2022 评估版。 近日消息&#xff0c;微软公司今天发布新闻稿&#xff0c;宣布…

迅镭激光高功率激光切割设备中标中国机械500强露笑集团!

近日&#xff0c;迅镭激光与中国机械500强、中国民营企业制造业500强露笑集团达成战略合作&#xff0c;成功签约10套激光切割设备&#xff0c;其中包括5套GK系列高功率光纤激光切割机、2套GI系列高功率光纤激光切割机、3套光纤激光切管机&#xff0c;这些设备将用于露笑集团全资…

CSS概念

1、CSS与HTML结合方式 1.1 第一种方式 内联/行内样式 就是在我们的HTML标签上通过style属性来引用CSS代码。 优点:简单方便 &#xff1b; 缺点:只能对一个标签进行修饰。 1.2 第二种方式 内部样式 我们通过<style>标签来声明我们的CSS. 通常<style>标签我们推荐写在…

性能优化——分库分表

1、什么是分库分表 1.1、分表 将同一个库中的一张表&#xff08;比如SPU表&#xff09;按某种方式&#xff08;垂直拆分、水平拆分&#xff09;拆分成SPU1、SPU2、SPU3、SPU4…等若干张表&#xff0c;如下图所示&#xff1a; 1.2、分库 在表数据不变的情况下&#xff0c;对…

CPU的基本知识介绍

相信大家对CPU一定不陌生了,从小就耳濡目染了,不过想搞清楚CPU的工作原理,并不是一件简单的事情&#xff0c;毕竟迄今为止,CPU的制造仍然是人类科技的巅峰。下面我们以SIT测试为主&#xff0c;简单的介绍下CPU,至于其工作原理,就不在此做详述。 CPU(Central Processing Unit&a…

配置开启Hive远程连接

配置开启Hive远程连接 Hive远程连接默认方式远程连接Hive自定义身份验证类远程连接Hive权限问题额外说明 Hive远程连接 要配置Hive远程连接&#xff0c;首先确保HiveServer2已启动并监听指定的端口 hive/bin/hiveserver2检查 HiveServer2是否正在运行 # lsof -i:10000 COMMA…

12. Oracle中case when详解

格式&#xff1a; case expression when condition_01 then result_01 when condition_02 then result_02 ...... when condition_n then result_n else result_default end 表达式expression符合条件condition_01&#xff0c;则返回…

[matlab]matlab配置mingw64编译器

第一步&#xff1a;下载官方绿色版本mingw64编译器然后解压放到一个非中文空格路径下面 比如我mingw64-win是我随便改的文件名&#xff0c;然后添加环境变量&#xff0c;选择用户或者系统环境变量添加下面的变量 变量名&#xff1a; MW_MINGW64_LOC 变量值&#xff1a;自己的m…

Windows如何部署Redis

一、简介 Redis (Remote Dictionary Server) 是一个由意大利人 Salvatore Sanfilippo 开发的 key-value 存储系统&#xff0c;具有极高的读写性能&#xff0c;读的速度可达 110000 次/s&#xff0c;写的速度可达 81000 次/s 。 二、下载 访问 https://github.com/tporadows…

如何在jdk中导入CA证书

今天在我们的测试环境就出现了如下问题&#xff0c;单点登录对方的系统突然访问不了了&#xff0c;抓取后台日志发现是如下报错 javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath…

续写哈弗传奇,枭龙能行吗?

作者 | 魏启扬 来源 | 洞见新研社 上市发布会上&#xff0c;品牌高管信心满满的表示&#xff0c;“要做每个时代最好的SUV”。 上市45天后&#xff0c;这款新车迎来了第1万台车辆的下线。 上市3个月后&#xff0c;新车的影响力持续扩散&#xff0c;“四驱平权”的概念被越来…