7-1.渲染器与响应系统的结合
渲染器不仅能够渲染真实的DOM元素,它还是框架跨平台能力的关键。 我们暂时将渲染器限定在DOM平台。
function renderer(domString, container){
// 代码自己写
}
我们可以这样使用它:
renderer('<h1>Hello</h1>', documents.getElementById('app'))
我们将使用@vue/reactivity包提供的effect和ref。定义一个响应式数据count,它是一个ref,在副作用函数内调用renderer函数执行渲染。
// 代码自己写
7-2.渲染器的基本概念
- render表示什么?
- reanderer表示什么?
- vnode表示什么?
- mount表示什么?
- container表示什么?
渲染器和渲染是相同的吗?
首先,当调用createRenderer函数创建渲染器时,渲染器不仅包含render函数,还包含hydrate函数。
function createRenderer(){
// 请完成代码
}
有了渲染器,我们就可以用它来执行渲染任务了。
const renderer = createRenderer()
// 首次渲染
renderer.render(vnode, document.getElementById('app'))
除了首次渲染,还要执行更新动作。
const render = createRenderer()
// 首次渲染
renderer.render(oldVNode, document.getElementById('app'))
// 第二次渲染
renderer.render(newVNode, document.getElementById('app'))
首次渲染时把oldValue渲染到containder内,第二次渲染会将newValue和上次渲染的oldValue进行比较,试图找到并更新变更点。这个过程叫"打补丁"(或更新patch)。
function render(vnode, container){
// 如果新vnode存在,将其与旧vnode一起传递给patch函数,进行打补丁
// 如过旧vnode存在,且新vnode不存在,说明是卸载操作
// 只需要将container内的DOM清空
// 最后,把vnode存储到container._vnode下
}
假如我们连续三次调用renderer.render函数来执行渲染:
const render = createRenderer()
// 首次渲染
renderer.render(vnode1, document.getElementById('app'))
// 第二次渲染
renderer.render(vnode2, document.getElementById('app'))
// 第三次渲染
renderer.render(null, documents.getElementById('app'))
- 首次渲染: ...
- 第二次渲染: ...
- 第三次渲染: ...
接着来观察patch方法(此处不详细展开,后续跟进)
patch(n1, n2, container)
- 第一个参数n1: 旧vnode。
- 第二个参数n2: 新vnode。
- 第三个参数container: 容器。
// 暂时不需要写代码
7-3.自定义渲染器
什么是渲染器的跨平台能力?。
vnode对象:
const vnode = {
type: "h1",
children: "hello",
};
对于这样一个vnode,我们可以使用render函数渲染它:
const vnode = {
type: "h1",
children: "hello",
};
// 创建一个渲染器
const renderer = createRenderer();
// 调用render函数渲染该vnode
renderer.render(vnode, document.getElementById("app"));
为了完成渲染工作,我们需要补充patch函数:
function patch(n1, n2, container) {
// 如果n1不存在,意味着挂载,则调用mountElement函数完成挂载
// 如果n1存在,意味着打补丁,暂时省略
}
function mountElement(vnode, container){
// 创建DOM元素
// 处理子节点,如果子节点是字符串,代表元素具有文本节点
// 因此只需要设置元素的textContent属性即可
// 将元素添加到容器
}
挂载一个普通标签元素的工作已经完成,接着我们会发现一个大问题。mountElement函数中调用了大量依赖于浏览器的API,例如createElement, appendChild等。我们如何将这些API抽离呢?
我们可以将这些操作DOM的API作为配置项。在创建renderer时传入配置项。
const renderer = createRenderer({
createElement(tag) {
return document.createElement(tag);
},
setElementText(el, text) {
el.textContent = text;
},
insert(el, parent, anchor = null) {
parent.inserBefore(el, anchor);
},
});
重构一下patch方法。
耶真棒!