一、渲染器及相关概念的介绍
- 什么是渲染器
- 用来执行渲染任务的模块,是实现框架跨平台能力的关键
- 渲染器用途
- 把虚拟DOM渲染为特定平台上的真实元素(浏览器中是渲染为真实DOM元素)
- 什么是虚拟DOM(vdom/vnode)
- 由一个个节点组成的树型结构
- 什么是虚拟DOM(vdom/vnode)
- 把虚拟DOM渲染为特定平台上的真实元素(浏览器中是渲染为真实DOM元素)
- 挂载(mount)
- 渲染器把虚拟DOM节点渲染为真实DOM节点的过程
- 挂载点(container)
- 一个DOM元素,渲染器将它作为容器元素,并将内容渲染到其中
- 更新/打补丁(patch)
- 要渲染的容器已被渲染过,渲染器此时会使用newVNode与上一次渲染的oldVNode进行比较,试图找到更新变更点
二、渲染器的核心入口:patch
- patch接收参数:
- 旧vnode,新vnode,容器
- 功能:
- 实现打补丁、挂载
三、渲染器代码及实现效果
<!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>
打开浏览器控制台
四、渲染器设计的注意事项
- 想要设计通用的渲染器,第一步要做的就是将某个平台上的特有API抽离,可以选择将这些操作DOM的API作为配置项
- 自定义渲染器是通过抽象的手段,让核心代码不再依赖于平台特有的API,再通过支持个性化配置的能力实现跨平台
参考资源:霍春阳《Vue.js设计与实现》第七章!