实现一个精简React -- 虚拟DOM如何变成真实DOM?四步渐进式拆解(2)

216 阅读2分钟

众所周知,react、vue等前端框架都是通过操作虚拟dom,减少直接操作真实dom的频率,以提升性能。
因此需要先理解什么是虚拟dom是如何渲染成真实dom,这里使用渐进式的方式去理解如何使用vdom渲染dom。

直接操作DOM的烦恼

假设我们要在网页上显示一个带文字的<div id="app">,用原生JavaScript需要这样写: 使用原生js的方式去创建div节点,创建text节点,然后挂载到根节点

const dom = document.createElement('div')
dom.id = 'app'
document.getElementById('root').append(dom)

const textNode = document.createTextNode('')
textNode.nodeValue = 'hello world'
dom.append(textNode)

第一步:虚拟DOM初体验

虚拟DOM就像一份图纸,我们先描述想要的结构:

// 文字部分的图纸
const textEl = {
    type: 'TEXT_ELEMENT',
    props: {
        nodeValue: 'hello world',
        children: [] // 没有子元素
    }   
}

// div容器的图纸
const el = {
    type: 'div',
    props: {
        id: 'app',
        children: [textEl] // 包含文字节点
    }
}

然后按图施工:

const dom = document.createElement(el.type) 
dom.id = el.props.id 
document.getElementById('root').append(dom) 

const textNode = document.createTextNode('') 
textNode.nodeValue = textEl.props.nodeValue 
dom.append(textNode)

虽然还是要手动操作DOM,但已经能看出 虚拟DOM真实DOM 的对应关系。

第二步:动态生成图纸

前面的图纸是写死的,我们可以创建图纸生成器


// 文字图纸生成器
const createTextEl = (text) => ({
    type: 'TEXT_ELEMENT',
    props: { 
        nodeValue: text, 
        children: [] 
    }   
})

// 通用图纸生成器
const createElement = (type, props, ...children) => ({
    type,
    props: { 
        ...props, 
        children 
    }
})

// 生成一套图纸
const el = createElement('div', {id: 'app'}) 
const textEl = createTextEl('hello world') 

// 然后按图施工:
const dom = document.createElement(el.type) 
dom.id = el.props.id 

const textNode = document.createTextNode('') 
textNode.nodeValue = textEl.props.nodeValue 

document.getElementById('root').append(dom) 
dom.append(textNode)

现在可以像搭积木一样组合元素,生成任意复杂的结构。

第三步:自动化施工队

最后我们创建自动施工函数


function render(vdom, container) {
    // 1. 创建基础建材
    const dom = vdom.type === 'TEXT_ELEMENT' 
        ? document.createTextNode('') 
        : document.createElement(vdom.type)

    // 2. 添加细节(处理属性)
    Object.keys(vdom.props).forEach(key => {
        if(key !== 'children') dom[key] = vdom.props[key]
    })

    // 3. 递归处理房间(深度优先施工)
    vdom.props.children.forEach(child => {
        render(child, dom)
    })

    // 4. 交房入住
    container.append(dom)
}

// 使用示例
const textEl = createTextEl('hello world') 
const app = createElement('div', {id: 'app'}, textEl)
render(app, document.getElementById('root'))

这个过程就像:

  1. 先打好地基(创建基础节点)
  2. 刷墙铺地板(设置属性)
  3. 逐个装修每个房间(递归处理子节点)
  4. 整栋楼交付使用(插入容器)

为什么需要虚拟DOM?

  1. 性能优化:批量处理DOM操作,减少直接操作真实DOM的次数
  2. 跨平台能力:同一套虚拟DOM可以渲染到网页/手机/小程序
  3. 开发体验:用声明式语法描述界面,更接近自然思维

项目源码:github.com/Cuimc/mini-…