在本章之前,我们花费了很大的篇幅全面的讲解了一个普通渲染器的实现原理,它可以将
Virtual DOM渲染为 Web 平台的真实 DOM。本章我们将在上一章的基础上讲解更加高级的渲染器:自定义渲染器(Custom renderer)以及异步渲染。
# 自定义渲染器的原理
渲染器是围绕 Virtual DOM 而存在的,在 Web 平台下它能够把 Virtual DOM 渲染为浏览器中的真实 DOM 对象,通过前面几章的讲解,相信你已经能够认识到渲染器的实现原理,为了能够将 Virtual DOM 渲染为真实 DOM,渲染器内部需要调用浏览器提供的 DOM 编程接口,下面罗列了在出上一章中我们曾经使用到的那些浏览器为我们提供的 DOM 编程接口:
document.createElement / createElementNS:创建标签元素。document.createTextNode:创建文本元素。el.nodeValue:修改文本元素的内容。el.removeChild:移除 DOM 元素。el.insertBefore:插入 DOM 元素。el.appendChild:追加 DOM 元素。el.parentNode:获取父元素。el.nextSibling:获取下一个兄弟元素。document.querySelector:挂载Portal类型的VNode时,用它查找挂载点。
这些 DOM 编程接口完成了 Web 平台(或者说浏览器)下对 DOM 的增加、删除、查找的工作,它是 Web 平台独有的,所以如果渲染器自身强依赖于这些方法(函数),那么这个渲染器也只能够运行在浏览器中,它不具备跨平台的能力。换句话说,如果想要实现一个平台无关的渲染器,那么渲染器自身必须不能强依赖于任何一个平台下特有的接口,而是应该提供一个抽象层,将 “DOM” 的增加、删除、查找等操作使用抽象接口实现,具体到某个平台下时,由开发者决定如何使用该平台下的接口实现这个抽象层,这就是自定义渲染器的本质。
TIP
在下文中,我们将使用 “元素” 一词指代所有平台中的元素对象,例如在 Web 平台下 “元素” 一词指的就是 DOM 元素。
渲染器除了负责对元素的增加、删除、查找之外,它还负责修改某个特定元素自身的属性/特性,例如 Web 平台中元素具有 id、href 等属性/特性。在上一章中,我们使用 patchData 函数来完成元素自身属性/特性的更新,如下代码用于修改一个元素的类名列表(class):
// patchData.js
case 'class':
el.className = nextValue
break
这段代码同样也只能运行在浏览器中,为了渲染器能够跨平台,那么修改一个元素自身的属性/特性的工作也应该作为可自定义的一部分才行,因此,一个跨平台的渲染器应该至少包含两个可自定义的部分:可自定义元素的增加、删除、查找等操作、可自定义元素自身属性/特性的修改操作。这样对于任何一个元素来说,它的增删改查都已经变成了可自定义的部分,我们只需要“告知”渲染器在对元素进行增删改查时应该做哪些具体的操作即可。
接下来我们就着手将一个普通渲染器修改为拥有自定义能力的渲染器,在之前的讲解中,我们将渲染器的代码存放在了 render.js 文件中,如下是整个 render.js 文件的核心代码:
// 导出渲染器
export default function render(vnode, container) { /* ... */ }
// ========== 挂载 ==========
function mount(vnode, container, isSVG, refNode) { /* ... */ }
function mountElement(vnode, container, isSVG, refNode) { /* ... */ }
function mountText(vnode, container) { /* ... */ }
function mountFragment(vnode, container, isSVG) { /* ... */ }
function mountPortal(vnode, container) { /* ... */ }
function mountComponent(vnode, container, isSVG) { /* ... */ }
function mountStatefulComponent(vnode, container, isSVG) { /* ... */ }
function mountFunctionalComponent(vnode, container, isSVG) { /* ... */ }
