第七章 渲染器

99 阅读3分钟

渲染器相关的属于和概念

  1. 渲染器(renderer)和渲染(render)
    • 渲染器: 作用是把虚拟 DOM 渲染为特定平台上的真实元素。浏览器平台上,渲染器会把虚拟 DOM 渲染为真实 DOM 元素。
    • 渲染: render 动词,表示渲染的动作
  2. 虚拟DOM和虚拟节点(vnode)
    • 虚拟 DOM 和真实 DOM 的结构一样,都是由一个个节点组成的树型结构
    • 虚拟节点: 虚拟 DOM 是树型结构,这棵树中的任何一个 vnode 节点都可以是一棵子树,因此 vnode 和 vdom 有时可以替换使用。
  3. 挂载(mounted)
    • 渲染器把虚拟 DOM 节点渲染为真实 DOM 节点的过程叫作挂载,英文 mount, Vue.js 组件中的 mounted 钩子就会在挂载完成时触发。这就意味着,在 mounted 钩子中可以访问真实 DOM 元素。
  4. 打补丁(patch)
    • 首次渲染时已经把 oldVNode 渲染到 container 内了,第二次渲染 newVNode ,就不能简单地执行挂载动作了。渲染器会使用 newVNode 与上一次渲染的 oldVNode 进行比较,试图找到并更新变更点。这个过程叫作“打补丁”(或更新 ),英patch 。实际上,挂载动作本身也可以看作一利特殊的打补丁,它的特殊之处在于旧的 vnode 是不存在的。
function createRenderer() {
  // 打补丁
  function patch(n1, n2, container) {

  }
  // 渲染
  function render(vnode, container) {
	if (vnode) {
      // 新 vnode 存在,将其与旧 vnode 一起传递给 patch 函数进行打补丁
      patch(container._vnode, vnode, container)
    } else {
      if (container._vnode) {
        // 旧 vnode 存在,且新 vnode 不存在,说明是卸载(unmount)操作
        // 只需要将 container 内的 DOM 清空即可
        container.innerHTML = ''
      }
    }
    // 把 vnode 存储到 container._vnode 下,即后续渲染中的旧 vnode
    container._vnode = vnode
  }
  
  return {
    render
  }
}
// 创建渲染器
const renderer = createRenderer()

// 首次渲染, 执行 挂载 操作
renderer.render(vnode1, document.querySelector('#app'))
// 第二次渲染, 执行 打补丁 操作
renderer.render(vnode2, document.querySelector('#app'))
// 第三次渲染, 执行 卸载 操作
renderer.render(null, document.querySelector('#app'))

渲染器的基本实现

  • 渲染器不仅能够把虚拟 DOM 渲染为浏览器平台上的真实 DOM。过将渲染器设计为可配置的“通用”渲染器,即可实现渲染到任意目标平台上。
  • 为了让渲染器不直接依赖浏览器平台特有的 API,我们将这些来创建、修改和删除元素的操作抽象成可配置的对象。用户可以在调用createRenderer 函数创建渲染器的时候指定自定义的配置对象,从而实现自定义的行为。
// 渲染器的创建函数,接受一个option, 用于配置不同平台的渲染行为
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)
  }
  // 通用的打补丁(更新差异)
  function patch(n1, n2, container) {
    // 如果不存在旧vnode,直接直接指向挂载操作
    if (!n1) {
      mountElement(n2, container)
    } else {
      //如果有新旧DOM, 用diff对比差异后, 仅更新必要的部分
    }
  }

  function render(vnode, container) {
    if (vnode) {
      // 新 vnode 存在,将其与旧 vnode 一起传递给 patch 函数进行打补丁
      patch(container._vnode, vnode, container)
    } else {
      if (container._vnode) {
        // 旧 vnode 存在,且新 vnode 不存在,说明是卸载(unmount)操作
        // 只需要将 container 内的 DOM 清空即可
        container.innerHTML = ''
      }
    }
    // 把 vnode 存储到 container._vnode 下,即后续渲染中的旧 vnode
    container._vnode = vnode
  }
  // 返回render 函数
  return {
    render
  }
}

// 这是一个 渲染DOM的渲染器
const renderer = createRenderer({
  createElement(tag) {
    return document.createElement(tag)
  },
  setElementText(el, text) {
    el.textContent = text
  },
  insert(el, parent, anchor = null) {
    parent.insertBefore(el, anchor)
  }
})


// 这是一个渲染JOSN数据的渲染器
const renderer2 = 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
  }
})