《Vue.js 设计与实现》读书笔记 - 第7章、渲染器的设计
第7章、渲染器的设计
7.1 渲染器与响应系统的结合
- 渲染器需要有跨平台的能力。
- 在浏览器端会渲染为真实的 DOM 元素。
const { effect, ref } = VueReactivity // VueReactivity 是前几章实现的响应式代码
function renderer(domString, container) {
container.innerHTML = domString
}
const count = ref(1)
effect(() => {
renderer(`<h1>${count.value}</h1>`, document.getElementById('app'))
})
count.value++
通过简单的 innerHTML
可以实现渲染器,如果把渲染器放入响应式函数,就可以实现自动渲染。
7.2 渲染器的基本概念
- renderer 渲染器
- render 动词 渲染
- virtual DOM,vdom 虚拟 DOM
- virtual node,vnode,虚拟 node,构成 vdom 的节点
- 挂载,mount,虚拟 DOM 渲染为真实 DOM
- 挂载点,真实 DOM 挂载的位置,一个 DOM 元素,一般用 container 表示。
我们实现 createRenderer
函数,它会创建不同平台的渲染器。其中 render
用于浏览器。渲染分两种情况,挂载和后续渲染(存在旧节点),我们在内部使用 patch 去实现具体渲染(暂未实现)。
function createRenderer() {
// n1 旧node
// n2 新node
// container 容器
// patch可以用户挂载 也可以用于后续渲染
function patch(n1, n2, container) {}
function render(vnode, container) {
if (vnode) {
// 如果有新 vnode 就和旧 vnode 一起用 patch 处理
patch(container._vnode, node, container)
} else {
// 没有新 vnode 但是有旧 vnode 直接清空 DOM 即可
if (container._vnode) {
container.innerHTML = ''
}
}
// 把旧 vnode 缓存到 container
container._vnode = vnode
}
function hydrate(vnode, container) {
// 服务端渲染
}
return {
render,
hydrate,
}
}
7.3 自定义渲染器
把平台相关的函数提取出来,并通过参数传入,然后封装多平台通用的渲染器。
如下,实现了 mountElement
函数,用于 patch
函数在挂载时使用,而挂载要依赖平台的实现,比如创建元素,插入元素,我们把这些通过 options
统一传入。
function createRenderer(options) {
const { createElement, insert, setElementText } = options
function mountElement(vnode, container) {
const el = createElement(vnode.type)
if (typeof vnode.children === 'string') {
setElementText(el, vnode.children)
}
insert(el, container)
}
// n1:旧node,n2:新node,container:容器
function patch(n1, n2, container) {
if (!n1) {
// 挂载
mountElement(n2, container)
} else {
// 打补丁,暂时省略
}
}
function render(vnode, container) {
if (vnode) {
// 如果有新 vnode 就和旧 vnode 一起用 patch 处理
patch(container._vnode, vnode, container)
} else {
// 没有新 vnode 但是有旧 vnode 直接清空 DOM 即可
if (container._vnode) {
container.innerHTML = ''
}
}
// 把旧 vnode 缓存到 container
container._vnode = vnode
}
function hydrate(vnode, container) {
// 服务端渲染
}
return {
render,
hydrate,
}
}
当我们实现浏览器端的渲染器时,可以传入浏览器的 API 实现的函数。
const renderer = createRenderer({
createElement(tag) {
return document.createElement(tag)
},
setElementText(el, text) {
el.textContent = text
},
insert(el, parent, anchor = null) {
parent.insertBefore(el, anchor)
},
})
renderer.render(
{
type: 'h1',
children: 'hello',
},
document.getElementById('app')
)
如上代码,可以在页面渲染出 h1
标签显示 hello
。我们也可以通过传入自己实现的对应 API 实现自定义渲染器。
const rendererCustom = createRenderer({
createElement(tag) {
console.log(`创建元素 ${tag}`)
return { tag }
},
setElementText(el, text) {
console.log(`设置 ${JSON.stringify(el)} 的文本内容: ${text}`)
el.text = text
},
insert(el, parent, anchor = null) {
console.log(`将 ${JSON.stringify(el)} 添加到: ${JSON.stringify(parent)} 下`)
parent.children = el
},
})
rendererCustom.render(
{
type: 'h1',
children: 'hello',
},
{ type: 'root' }
)
.