第七章-渲染器的设计

72 阅读1分钟

1、渲染器与响应系统的结合

响应系统和渲染器之间的管理: 利用响应系统的能力, 自动调用渲染器完成页面的渲染和更新

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app"></div>
<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script> // vue的reactivity包
<script src="renderer.js" defer></script>
</body>
</html>
const { effect, ref } = VueReactivity
function renderer(domString, container) {
  container.innerHTML = domString
}
const count = ref(1)
effect(() => {
  renderer(`<h1>${count.value}</h1>`, document.getElementById('app'))
})
count.value++

2、渲染器的基本概念

渲染器: 渲染器的作用是把虚拟DOM渲染为特定平台上的真实元素。比如在浏览器平台上, 渲染器会把虚拟DOM渲染为真实的DOM元素

虚拟DOM: vdom或者vnode

挂载: 把虚拟DOM节点渲染为真实的DOM节点的过程

容器: 渲染器通常需要接受一个挂载点作为参数, 用来指定具体的挂载位置, 这个挂载点就是容器(container)

function createRenderer() {
  function render(vnode, container) {
  
  }
  function hydrate(vnode, container) {
  
  }
  return {
    render,
    hydrate
  }
}

问: 为什么需要用createRenderer, 而不是直接使用render函数

答:渲染器是更加宽泛的概念,render只是其中的一部分。 渲染器不仅可以用来渲染, 还可以用来(在同构渲染的情况下)激活已有的DOM元素, 甚至创建应用的createApp也是渲染器的一部分

渲染器中的渲染包括了挂载, 更新和卸载的功能

function createRenderer() {
  // n1旧vnode n2新vnode, container容器
  function patch(n1, n2, container) {
  
  }
  function render(vnode, container) {
    if(vnode){
       // 打补丁(挂载也是一种特殊的打补丁)
      patch(container._vnode, vnode, container)  // 挂载或者更新
    } else {
       if(container._vnode) {
         container.innerHTML = ";"  // 卸载
       }
    }
    container._vnode = vnode; // 存储当前的节点
  }
}
const renderer = createRenderer();
const vnode1 = { type: "h1", children: "文字"};
const vnode2 = { type: "h2", children: "文字"};
renderer.render(vnode1, document.getElementById("app"));
renderer.render(vnode2, document.getElementById("app"));
renderer.render(null, document.getElementById("app"));

3、自定义渲染器

目标是设计一个不依赖浏览器平台的通用渲染器, 涉及浏览器的API应该抽取出来, 后续作为配置项

function createRenderer(options) {
  const{ createElement, setElementText, insert } = options;
  function mountElement(vnode, container) {
     const el = createElement(vnode.type);
     if(typeof vnode.children === "string") {
       setElementText(el, vnode.children)
     }
    insert(el, container);
  }
  // n1旧vnode n2新vnode, container容器
  function patch(n1, n2, container) {
    if(!n1) {
      mountElement(n2, container);
    }
  }
  function render(vnode, container) {
    if(vnode){
       // 打补丁(挂载也是一种特殊的打补丁)
      patch(container._vnode, vnode, container)
    } else {
       if(container._vnode) {
         container.innerHTML = ";"
       }
    }
    container._vnode = vnode;
  }
  return {
    render,
  }
}

涉及的配置如下

const renderer = createRenderer({
  createElement(tag){
    return document.createElement(tag);
  },
  setElementText(el, text){
    el.textContent = text;
  },
  insert(el, parent, anchor = null) {
    parent.insertBefore(el, anchor)  // parent父节点 el需要插入的节点  anchor插入时需要插入在这个节点前面
  }
});

可以更改配置以适应平台

const renderer = createRenderer({
  createElement(tag){
    console.log("创建元素"+ tag)
    return {tag};
  },
  setElementText(el, text){
    console.log(`设置元素${JSON.stringify(el)}的文本内容${text}`)
    el.textContent = text;
  },
  insert(el, parent, anchor = null) {
    console.log(`将元素${JSON.stringify(el)}添加到${parent}下`)
    parent.children = el;
  }
});
// 测试结果
 创建元素h1
 设置元素{"tag":"h1"}的文本内容文字
 将元素{"tag":"h1","textContent":"文字"}添加到{"type":"parent"}下