Vuejs设计与实现 —— 渲染器

简介: Vuejs设计与实现 —— 渲染器

image.png


基本概念

了解渲染器所涉及的基本概念,有助于更好的理解框架 API 的设计。

渲染器 & 渲染

通常使用名词 renderer 来表示 "渲染器",使用动词 render 来表示 "渲染"渲染器 的作用是把虚拟 DOM渲染 为特定平台上的真实元素,例如,在浏览器平台上,渲染器会把 虚拟 DOM 渲染为 真实 DOM 元素。

虚拟 DOM & 虚拟节点

虚拟 DOM 通常使用英文 virtual DOM 表示,简写为 vdom虚拟 DOM真实 DOM 结构是一样的,都是由一个个节点组成的树形结构,而 虚拟节点 使用 virtual node 来表示,简写为 vnode虚拟 DOM 是树形结构,其中的任何一个节点 vnode 都可以代表一颗子树,因此 vnodevdom 是可以替换使用的。

挂载

渲染器虚拟 DOM 节点渲染为 真实 DOM 节点的过程叫作 挂载,英文表示为 mount,例如 在 Vue.js 组件中的 mounted 钩子就会在挂载完成时触发,这就意味着可以在这个钩子中访问到 真实 DOM 元素。

通过一下代码来辅助理解:

function createRenderer(){
       fucntion render(vnode, container){
        ...
       }
       fucntion hydrate(){
        ...
       }
      // 返回渲染函数和 createApp
      return {
        render,
        hydrate,
        createApp: createAppAPI(render, hydrate)
      }
 }
复制代码

其中 createRenderer 函数用来创建一个渲染器,调用 createRenderer 函数后会得到一个 render 函数,这个 render 函数会以 container 为挂载点,将 vnode 渲染为真实 DOM 并进行挂载。

为什么需要 createRenderer 函数?

渲染器渲染 是不同的,渲染器 是更加宽泛的概念,它包含了 渲染,渲染器不仅可以用来渲染,还可以用来激活已有的 DOM 元素,这通常发生在 同构渲染 的情况下。可以看到,当创建渲染器时,渲染器除了包含 render 函数外,还包含了 hydrate 函数,专门用于处理服务端渲染。

// 创建对应平台的渲染器
cosnt renderer = createRenderer()
// 首次渲染,进行挂载
renderer.render(vnode, document.querySelector('#app'))
// 后续渲染,进行更新
renderer.render(newVnode, document.querySelector('#app'))
复制代码

render 函数的实现思路

为了便于理解,先看下面的 render 部分的伪代码:

function createRenderer() {
  function render(vnode, container) {
    if (vnode) {
      // 新的 vnode 存在,将其与旧的 vnode 一起传递给 patch 函数,进行补丁(挂载 或 更新)
      patch(container._vnode, vnode, container)
    } else {
      if(container._vnode){
        // 新的 vnode 不存在,旧的 vnode 存在,说明当前属于 unmount 操作
        // 这里简单的通过 container.innerHTML 将 container 的内容清空
        container.innerHTML = ''
      }
    }
    // 将新的 vnode 存储到 container._vnode 中,即后续渲染中旧的 vnode
    container._vnode = vnode
  }
  return {
    render,
  }
}
复制代码

假设连续使用三次 renderer.render 函数执行渲染,如下:

// 容器元素
const app = document.querySelector("#app");
// 创建渲染器
const renderer = createRenderer();
// 首次渲染
renderer.render(vnode1, app);
// 第二次渲染
renderer.render(vnode2, app);
// 第三次渲染
renderer.render(null, app);
复制代码
  • 首次渲染时,会将 vnode1 渲染为 真实 DOM,渲染完成后,vnode1 会被存储到 container._vnode 中,作为后续渲染中的 旧 vnode 使用
  • 第二次渲染时,旧 vnode 存在,此时会把 vnode2 作为 新 vndoe,并将 新旧 vnode 传递给 patch 函数进行补丁
  • 第三次渲染时,新 vnode 节点为 null,即什么都不渲染,但此时容器中渲染的是 vnode2 的内容,所以渲染器需要清空容器,当然直接通过 innerHTML = '' 清空的方式是有问题的,这里只是用于表示达到清空的目的

上面的三次渲染分别对应着:挂载、更新、卸载 的过程,patch 函数是整个渲染器的核心入口,它包含了重要的渲染逻辑,其中 patch 函数的各个参数:

function patch(n1, n2, container){...}
复制代码
  • n1 代表 旧 vnode 节点
  • n2 代表 新 vnode 节点
  • container 代表真实的容器元素

自定义渲染器

渲染器不仅应该能够把 虚拟 DOM 渲染为浏览器平台上的 真实 DOM,也应该能实现在渲染到任意平台上,这就意味需要将渲染器中浏览器特定的 API 进行抽象,这样就可以使得渲染器的核心不依赖于浏览器。在此基础上,再为那些抽离 API 提供可配置的接口,既可实现渲染器跨平台的能力

抽离和平台强相关的 API

首先针对 patch 函数进行一个简单的实现,并且通过 mountElement 完成挂载操作,如下:

// 渲染器
function createRenderer() {
  // mountElement
  function mountElement(vnode, container) {
    // 创建 dom 元素
    const el = document.createElement(vnode.type)
    // 若子节点是字符串,则代表是文本内容
    if (typeof vnode.children === 'string') {
        el.textContext = el.children;
    }
    // 将子元素添加到容器中
    container.appendChild(el)
  }
  // patch
  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,
  }
}
复制代码

通过上述内容,我们的目标是设计一个不依赖于浏览器平台的通用渲染器,但是在 mountElement 函数内调用了大量依赖于浏览器的 API(如:createElement、appendChild、textContext),因此第一步就是将这些依赖于浏览器的 API 进行抽离。

可以在创建渲染器时通过传入对应的配置项,如下:

// 在创建 renderer 时传入配置项
const renderer = createRenderer({
  // 用于创建元素
  createdElement(tag) {
    return document.createElement(tag)
  },
  // 用于设置元素的文本节点
  setElementText(el, text) {
    el.textContent = text
  },
  // 用于在给定的 parent 下添加指定元素
  insert(el, parent, anchor = null) {
    parent.insertBefore(anchor, el)
  }
})
复制代码

于是在渲染器内就可以通过配置项 options 对获取对应操作 DOM 的 API 了:

// 渲染器
function createRenderer(options) {
  // 通过配置项获取操作 DOM 的 API
  const {
    createElement,
    setElementText,
    insert
  } = options;
  // mountElement
  function mountElement(vnode, container) {
    // 创建 dom 元素
    const el = createElement(vnode.type)
    // 若子节点是字符串,则代表是文本内容
    if (typeof vnode.children === 'string') {
      setElementText(el.children);
    }
    // 将子元素添加到容器中
    insert(el, container)
  }
  // patch
  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,
  }
}
复制代码

重构后的代码,已经不再直接依赖于浏览器特有的 API 了,并且通过传入不同的配置项,就能够完成非浏览器环境下的渲染工作。

最后

有了对渲染器最基本的了解,在结合 Vue.js 源码的学习会有更深刻的理解,为什么 vue.js 要如此设计其 API


目录
相关文章
|
2月前
|
JavaScript
vue异步渲染
vue异步渲染
|
19天前
|
监控 JavaScript 前端开发
Vue 异步渲染
【10月更文挑战第23天】Vue 异步渲染是提高应用性能和用户体验的重要手段。通过理解异步渲染的原理和优化策略,我们可以更好地利用 Vue 的优势,开发出高效、流畅的前端应用。同时,在实际开发中,要注意数据一致性、性能监控和调试等问题,确保应用的稳定性和可靠性。
|
2月前
|
JavaScript 前端开发
Vue学习笔记8:解决Vue学习笔记7中用v-for指令渲染列表遇到两个问题
Vue学习笔记8:解决Vue学习笔记7中用v-for指令渲染列表遇到两个问题
|
2月前
|
JavaScript 前端开发 API
Vue学习笔记7:使用v-for指令渲染列表
Vue学习笔记7:使用v-for指令渲染列表
|
2月前
|
人工智能 JavaScript 索引
Duplicate keys detected: This may cause an update error.【Vue遍历渲染报错的解决】
这篇文章讨论了在Vue中进行列表渲染时遇到的“Duplicate keys detected”错误。这个错误通常发生在使用 `v-for` 指令渲染列表时,如果没有为每个循环项指定一个唯一的 `key` 属性,或者指定的 `key` 属性值重复了。文章提供了导致错误的原始代码示例,并给出了修正后的代码,通过在 `key` 绑定中加入索引确保 `key` 的唯一性。此外,文章还解释了为什么需要唯一 `key` 以及如何解决这个问题。
Duplicate keys detected: This may cause an update error.【Vue遍历渲染报错的解决】
|
2月前
|
JavaScript 前端开发 UED
组件库实战 | 用vue3+ts实现全局Header和列表数据渲染ColumnList
该文章详细介绍了如何使用Vue3结合TypeScript来开发全局Header组件和列表数据渲染组件ColumnList,并提供了从设计到实现的完整步骤指导。
|
3月前
|
JavaScript 算法 前端开发
"揭秘Vue.js的高效渲染秘诀:深度解析Diff算法如何让前端开发快人一步"
【8月更文挑战第20天】Vue.js是一款备受欢迎的前端框架,以其声明式的响应式数据绑定和组件化开发著称。在Vue中,Diff算法是核心之一,它高效计算虚拟DOM更新时所需的最小实际DOM变更,确保界面快速准确更新。算法通过比较新旧虚拟DOM树的同层级节点,递归检查子节点,并利用`key`属性优化列表更新。虽然存在局限性,如难以处理跨层级节点移动,但Diff算法仍是Vue高效更新机制的关键,帮助开发者构建高性能Web应用。
68 1
|
3月前
|
JavaScript
Vue学习之--------列表渲染、v-for中key的原理、列表过滤的实现(2022/7/13)
这篇博客文章详细介绍了Vue中列表渲染的基础知识、`v-for`指令的使用、`key`的原理和列表过滤的实现。通过代码实例和测试效果,展示了如何遍历数组和对象、使用`key`属性优化渲染性能,以及如何实现列表的动态过滤功能。
Vue学习之--------列表渲染、v-for中key的原理、列表过滤的实现(2022/7/13)
|
3月前
|
JavaScript 前端开发
Vue学习之--------绑定样式、条件渲染、v-show和v-if的区别(2022/7/12)
这篇博客文章讲解了Vue中绑定样式和条件渲染的方法,包括类样式绑定的不同写法、`v-show`和`v-if`的条件渲染区别以及它们的使用场景和特点,并通过代码实例和测试效果来展示具体应用。
Vue学习之--------绑定样式、条件渲染、v-show和v-if的区别(2022/7/12)
|
3月前
|
JavaScript UED
强制 Vue 重新渲染组件的5种方法,解决你开发过程中数据和视图无法同步的Bug。
强制 Vue 重新渲染组件的5种方法,解决你开发过程中数据和视图无法同步的Bug。