vue3简单渲染器实现

97 阅读2分钟

一、渲染器及相关概念的介绍

  1. 什么是渲染器
    • 用来执行渲染任务的模块,是实现框架跨平台能力的关键
  2. 渲染器用途
    • 把虚拟DOM渲染为特定平台上的真实元素(浏览器中是渲染为真实DOM元素)
      • 什么是虚拟DOM(vdom/vnode)
        • 由一个个节点组成的树型结构
  3. 挂载(mount)
    • 渲染器把虚拟DOM节点渲染为真实DOM节点的过程
  4. 挂载点(container)
    • 一个DOM元素,渲染器将它作为容器元素,并将内容渲染到其中
  5. 更新/打补丁(patch)
    • 要渲染的容器已被渲染过,渲染器此时会使用newVNode与上一次渲染的oldVNode进行比较,试图找到更新变更点

二、渲染器的核心入口:patch

  1. patch接收参数:
    • 旧vnode,新vnode,容器
  2. 功能:
    • 实现打补丁、挂载

三、渲染器代码及实现效果

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>渲染器实现</title>
</head>
<body>
    <div id="app"></div>
    <!--引入@vue/reactivity包提供的响应式API(名为VueReactivity)-->
    <script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
    <script>
        const { effect, ref } = VueReactivity // 通过VueReactivity得到effect, ref两个API

        function createRenderer(options) {
            // 通过 options 得到操作 DOM 的 API
            const {
                createElement,
                insert,
                setElementText
            } = options
            // mountElement 函数
            function mountElement(vnode, container) {
                // 创建 DOM 元素
                const el = createElement(vnode.type)
                // 处理子节点,如果子节点是字符串,代表元素有文本节点
                if (typeof vnode.children === 'string') {
                    // 因此只需要设置元素的 textContent 属性即可
                    setElementText(el, vnode.children)
                }
                // 将元素添加到容器中
                insert(el, container)
            }
            // patch 函数
            function patch(n1, n2, container) {
                // 如果 n1 不存在,意味着挂载,则调用 mountElement 函数完成挂载
                if (!n1) {
                    mountElement(n2, container)
                } else {
                    // n1 存在,意味着打补丁
                }
            }
            function render(vnode, container) {
                if (vnode) {
                    // 新 vnode 存在, 将其与旧 vnode 一起传递给 patch 函数,进行打补丁
                    patch(container._vnode, vnode, container)
                } else {
                    if (container._vnode) {
                        // 旧 vnode 存在,且新 vnode 不存在,说明是卸载( unmounted )操作
                        // 只需清空 container 内的 DOM 即可
                        container.innerHTML = ''
                    }
                }
                // 把 vnode 存储到 container_vnode 下,即后继渲染中的旧 vnode
                container._vnode = vnode
            }
            return {
                render
            }
        }

        // 创建一个 vnode 对象
        const vnode = { 
            type: 'h1', // vnode 对象的类型
            children: 'hello'
        }
         
        // 使用一个对象模拟挂载点
        const container = { type: 'root' }
        // 创建一个渲染器
        const renderer = createRenderer({// 把操作 DOM 的 API 封装成一个对象,这样相关函数就能通过配置项取得操作DOM
            // 实现自定义渲染器
            // 用于创建元素
            createElement(tag) {
                console.log(`创建元素 ${tag}`);
                return { tag }
            }, 
            // 用于设置元素的文本节点
            setElementText(el, text) {
                console.log(`设置 ${JSON.stringify(el)} 的文本内容:${text}`);
                el.textContent = text
            },
            // 用于在给定的 parent 下添加指定元素
            insert(el, parent, anchor = null) {
                console.log(`将 ${JSON.stringify(el)} 添加到 ${JSON.stringify(parent)} 下`);
                parent.children = el
            }
        })

        // 调用 render 函数渲染该 vnode
        renderer.render(vnode, container)
    </script>
</body>
</html>

打开浏览器控制台 image.png

四、渲染器设计的注意事项

  1. 想要设计通用的渲染器,第一步要做的就是将某个平台上的特有API抽离,可以选择将这些操作DOM的API作为配置项
  2. 自定义渲染器是通过抽象的手段,让核心代码不再依赖于平台特有的API,再通过支持个性化配置的能力实现跨平台

参考资源:霍春阳《Vue.js设计与实现》第七章!