第七章、渲染器的设计
渲染器就是用来完成渲染任务的,在浏览器平台就是用来渲染真实 dom 元素的。结合响应式系统,实现一个最简单的渲染器。
// 最简单的渲染函数,直接挂载dom
function renderer(domString, container) {
container.innerHTML = domString
}
// 定义响应式数据
const count = ref(1)
// 副作用函数中执行渲染函数
effect(() => {
renderer(`<h1>${count.value}</h1>`, document.getElementById('app'))
})
// 数据变化后渲染函数会重新执行
count.value++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
渲染器可以在多平台使用,为各个平台创建不同的渲染函数。
// 创建渲染器,返回各个平台的渲染方法
function createRenderer() {
// 客户端渲染方法
function render(vnode, container) {}
// 服务端渲染方法
function hydrate(vnode, container) {}
return {
render,
hydrate,
}
}
// 调用渲染器,创建渲染函数
const renderer = createRenderer()
// 执行渲染
renderer.render(vnode, document.getElementById('app'))
// 更新的时候也是调用渲染函数,不过更新的时候会做新旧vnode的对比操作
renderer.render(newVnode, document.getElementById('app'))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
渲染函数也要承载更新的过程,这个过程叫 patch,当然首次挂载也是一种特殊的更新。
function render(vnode, container) {
// 如果新的vnode存在,将新旧vnode传给patch函数进行对比操作
if (vnode) {
patch(container._vnode, vnode, container)
} else {
// 当新node不存在,且旧node存在,表示本次更新是卸载操作,直接清空dom
if (container._vnode) {
container.innerHTML = ''
}
}
// 操作完成之后讲本次的vnode挂载在container上,成为后续渲染中的旧vnode
container._vnode = vnode
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
在渲染器中定义各种渲染用的函数完善我们的渲染器。
function createRenderer() {
// 挂载函数,生成dom并挂载到容器上
function mountElement(vnode, container) {
const el = document.createElement(vnode.type)
//
if (typeof vnode.children === 'string') {
el.textContent = vnode.children
}
container.appendChild(el)
}
function patch(n1, n2, container) {
if (!n1) {
// n1不存在,表示挂载操作
mountElement(n2, container)
} else {
// n1存在,表示更新操作
}
}
function render(vnode, container) {
if (vnode) {
patch(container._vnode, vnode, container)
} else {
if (container._vnode) {
container.innerHTML = ''
}
}
container._vnode = vnode
}
return {
render,
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
可以通过配置项的形式,传入我们自定义的渲染函数,这样可以让渲染器不依赖平台,成为一个各平台通用的渲染器函数。
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)
}
}
// 创建渲染器时传入对应平台的渲染函数
const renderer = createRenderer({
createElement(tag) {
return document.childElement(tag)
},
setElementText(el, text) {
el.textContent = text
},
insert(el, parent, anchor = null) {
parent.insertBefore(el, anchor)
},
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28