Vnode到真实DOM是如何转变的?

本文探讨Vue3.0中组件的渲染过程,从创建Vnode到生成真实DOM。详细解释了组件渲染的步骤,包括初始化、创建Vnode、渲染Vnode,以及patch函数在挂载和更新组件时的角色。文章还深入解析了渲染器对象的创建和如何通过Vnode描述组件结构。

一、写在前面
在vue开发中,组件是非常重要的概念,但是我们在编写组件的时候,是否知道其内部是如何进行运转的。本文将总结一下vue3.0中组件是如何进行渲染的?
二、内容
在我们编写组件代码时,会经常编写如下所示模板代码。

<template>
	<div>
		<p>hello world</p>
	</div>
</template>

从上述表现上看,组件的模板决定了组件生成的DOM标签,而在vuejs内部,一个组件如果想要生成真正的DOM,需要经过如下几个步骤。
在这里插入图片描述
如上图所示,需要经过创建vnode,渲染vnode,以及生成DOM的三个过程。接下来我们将从程序入口开始,一步一步看真实DOM是如何生成的。
1、应用程序初始化

// 在 Vue.js 3.0 中,初始化一个应用的方式如下
import {
   
    createApp } from "vue";
import App from "./app";
const app = createApp(App);
app.mount("#app");

如上图所示,我们可以看到入口函数是createApp

const createApp = (...args) => {
   
   
  // 创建 app 对象
  const app = ensureRenderer().createApp(...args);
  const {
   
    mount } = app;
  // 重写 mount 方法
  app.mount = (containerOrSelector) => {
   
   
    // ...
  };
  return app;
};

上述就是createApp主要做的事,一个是创建app对象,另一个是重写app.mount方法。
2、创建app对象
首先我们首先执行代码:

const app = ensureRenderer().createApp(...args);

其中ensureRenderer()来创建一个渲染器对象,它内部代码为:

// 渲染相关的一些配置,比如更新属性的方法,操作 DOM 的方法
const rendererOptions = {
   
   
  patchProp,
  ...nodeOps,
};
let renderer;
// 延时创建渲染器,当用户只依赖响应式包的时候,可以通过 tree-shaking 移除核心渲染逻辑相关的代码
function ensureRenderer() {
   
   
  return renderer || (renderer = createRenderer(rendererOptions));
}
function createRenderer(options) {
   
   
  return baseCreateRenderer(options);
}
function baseCreateRenderer(options) {
   
   
  function render(vnode, container) {
   
   
    // 组件渲染的核心逻辑
  }

  return {
   
   
    render,
    createApp: createAppAPI(render),
  };
}
function createAppAPI(render) {
   
   
  // createApp createApp 方法接受的两个参数:根组件的对象和 prop
  return function createApp(rootComponent, rootProps = null) {
   
   
    const app = {
   
   
      _component: rootComponent,
      _props: rootProps,
      mount(rootContainer) {
   
   
        // 创建根组件的 vnode
        const vnode = createVNode(rootComponent, rootProps);
        // 利用渲染器渲染 vnode
        render(vnode, rootContainer);
        app._container = rootContainer;
        return vnode.component.proxy;
      },
    };
    return app;
  };
}

首先ensureRenderer()来延时创建渲染器。
好处:

当用户值依赖响应式包的时候,就不会创建渲染器。
可以通过tree-shaking的方式来移除核心渲染逻辑相关的代码。
我都其理解是:因为我们在调用createApp才会执行`ensureRenderer()`方法,如果我们只使用响应式的包的时候,并不使用渲染器,此时我们就可以在打包的时候,使用tree-shaking来将没有使用到的函数取消。

其次通过createRenderer创建一个渲染器,这个渲染器内部存在一个createApp方法,接收了rootComponentrootProps两个参数。
我们在应用层面执行createAp(App)方法时 ,会把App组件对象作为跟组件传递给rootComponet,这样createApp内部就会创建一个App对象。他会提供mount方法,这个方法是用来挂载组件的。
值得注意的是:app在创建对象时,vue利用闭包和函数的柯里化的技巧,很好的实现了参数保留。
3、重写app.mount方法
createApp返回的app兑现已经拥有了mount方法,那为什么还要重写?

1、为了支持跨平台。
createApp函数内部的app.mount方法是一个标准的可跨平台的组件渲染流程。代码如下所示。

                
<think>好的,用户想了解虚拟DOM真实DOM之间的主要区别以及它们的工作原理。我需要结合之前的回答和提供的引用内容来组织答案。 首先,用户之前的问题是关于DOM的,现在转向虚拟DOM真实DOM的区别,所以需要先明确两者的定义。真实DOM是浏览器中的实际结构,操作起来成本高。而虚拟DOM是内存中的轻量表示,用来优化性能。 根据引用内容,虚拟DOM通过Diff算法比较变化,批量更新真实DOM,减少重排和重绘。比如引用[2]提到虚拟DOM保证性能下限,运行效率比不正确操作真实DOM高。这里需要解释Diff算法和批处理机制。 另外,引用[4]提到key的作用,帮助高效更新虚拟DOM,避免不必要的元素替换。这点在列表渲染中很重要,需要举例说明。 还要区分两者的结构差异。真实DOM包含更多属性和方法,而虚拟DOM简化了这些,引用[3]指出Vue用虚拟DOM优化性能。可能需要对比两者的属性和更新方式。 工作原理部分,需要分步骤说明:创建虚拟DOM、状态变化生成新虚拟DOM、Diff比较、批量更新真实DOM。引用[1]提到虚拟DOMDOM Diff的实现原理,这里可以展开。 用户可能想知道为什么虚拟DOM更高效,所以需要强调减少直接操作真实DOM的次数,以及跨平台的优势,比如React Native,引用[2]提到框架如Vue和React的处理。 还要注意用户可能对key的作用有疑问,引用[4]和[5]提到key的唯一性和在过渡效果中的应用,可以补充例子说明没有key可能导致的问题。 最后,确保回答结构清晰,分点列出区别和原理,并附上相关问题。检查引用标记是否正确,使用用户提供的引用编号。</think>### 虚拟DOM真实DOM的区别及工作原理 #### 一、核心区别 1. **定义与物理存在** - **真实DOM**:浏览器解析HTML后生成的树形结构对象,包含完整的节点属性、样式、事件等,直接映射页面内容。操作真实DOM会触发浏览器的重排(Reflow)和重绘(Repaint),性能消耗较高[^3][^4]。 - **虚拟DOM**:内存中轻量化的JavaScript对象(如React的`ReactElement`或Vue的`VNode`),仅保留真实DOM的关键信息(如标签名、属性、子节点)。它是真实DOM的抽象表示,不直接操作页面[^1][^3]。 2. **更新机制** - **真实DOM**:直接修改会立即触发渲染流程(如修改`div.style.color`),频繁操作会导致性能下降。 - **虚拟DOM**:通过**Diff算法**比较新旧虚拟DOM树的差异(如节点类型、属性、子节点顺序),仅将**最小化更新**批量应用到真实DOM,减少渲染次数[^1][^2]。 ```javascript // 虚拟DOM示例(React) const vNode = { type: 'div', props: { className: 'box', children: 'Hello' } }; ``` 3. **性能优化方向** - **真实DOM**:需手动优化(如合并多次修改、使用文档片段`DocumentFragment`),否则易引发性能问题。 - **虚拟DOM**: - **自动批处理更新**:框架(如React、Vue)自动合并多次状态变更,减少真实DOM操作次数。 - **Diff算法的启发式策略**:通过`key`标识节点(如列表项),避免不必要的节点重建[^4][^5]。 ```javascript // 使用key优化列表渲染 <ul> {items.map(item => <li key={item.id}>{item.text}</li>)} </ul> ``` 4. **跨平台能力** - **真实DOM**:依赖浏览器环境,无法直接用于移动端或服务端渲染。 - **虚拟DOM**:作为中间层,可适配不同平台(如React Native渲染为原生组件,SSR生成静态HTML)[^2][^3]。 --- #### 二、工作原理(以React为例) 1. **虚拟DOM的创建** - 组件通过`render()`方法返回虚拟DOM树。 ```jsx function Component() { return <div className="title">Hello</div>; } ``` 2. **Diff算法执行** - **树对比**:逐层比较新旧虚拟DOM树,若根节点类型不同则直接替换整棵树。 - **组件对比**:相同类型的组件复用实例,触发更新逻辑(如`shouldComponentUpdate`)。 - **列表优化**:通过`key`标识元素,避免因顺序变化导致的全量重建[^4]。 3. **真实DOM的更新** - 将Diff结果转化为DOM操作指令(如`appendChild`、`setAttribute`),通过`requestIdleCallback`或微任务批量执行,减少渲染阻塞[^1][^2]。 --- #### 三、关键差异总结 | **对比维度** | **真实DOM** | **虚拟DOM** | |--------------------|--------------------------------|--------------------------------| | 操作成本 | 高(直接触发渲染流程) | 低(内存计算,批量更新) | | 更新效率 | 单次修改效率高,频繁操作性能差 | 通过Diff优化,适合频繁状态变更 | | 跨平台支持 | 仅浏览器环境 | 支持多平台(如React Native) | | 开发复杂度 | 需手动优化性能 | 框架自动处理差异更新 | --- ### 相关问题 1. 为什么虚拟DOM的Diff算法需要`key`属性?没有`key`会导致什么问题? 2. 虚拟DOM是否总能比直接操作真实DOM更快?什么场景下可能性能更低? 3. Vue和React的虚拟DOM实现有哪些差异? 4. 如何手动优化真实DOM操作以避免性能瓶颈?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值