一文详解Vue中渲染器的简单实现

 更新时间:2024年05月13日 09:20:27   作者:Lumen丶  
渲染器用于完成渲染操作,比如在浏览器平台上渲染器可以将虚拟DOM转换为真实DOM,本文将通过一个简单例子来带大家理解Vue中渲染器的工作过程,并通过代码示例讲解的非常详细,需要的朋友可以参考下

一、渲染器

渲染器用于完成渲染操作,比如在浏览器平台上渲染器可以将虚拟DOM转换为真实DOM。

二、一个简单例子理解Vue中渲染器的工作过程

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++

我们通过@vue/reactivity引用vue的响应式API,使用其中的effect(副作用函数)和ref(生成响应式数据)使得countrenderer建立联系,这样当 count变化时,会调用renderer处理渲染,将h1标签挂载到id为app的元素上。这就是一个简易的渲染器实现。

三、渲染器涉及的几种操作: 挂载、更新、卸载

假设我们已经有了一个渲染器renderer

const renderer = createRenderer()

有一个用于描述元素节点的数据vnode:类似下面的结构

// type为类型,children为子节点
const vnode = { 
    type: 'h1', 
    children: 'hello' 
}

可以看到这是一个内部有hello文本的<h1>节点

那么在渲染器实际工作过程中会有如下的几种情况:

renderer.render(vnode, document.querySelector('#app'))

此时涉及到的操作是挂载,只需要将vnode变为DOM元素放在app元素内部即可

renderer.render(newVNode, document.querySelector('#app'))

由于已经有旧的节点存在,所以不能简单的直接挂载,需要对新旧节点进行对比,找到需要更新的部分再改变,此时的操作就是更新,为了处理更新,渲染器中需要有对应的逻辑。

renderer.render(null, document.querySelector('#app')) 

新的节点为null则意味这渲染器需要清空app元素内的内容,此时涉及的操作是卸载

根据对以上三种情况的处理,可以将渲染器写为:

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
  }
}

使用createRenderer重复上面三次渲染,分析过程:

// 首次渲染 
renderer.render(vnode, document.querySelector('#app'))

// 第二次渲染 
renderer.render(newVNode, document.querySelector('#app'))

//第三次渲染
renderer.render(null, document.querySelector('#app'))
  • 首次渲染: vnode被渲染,并存在container._vnode中;
  • 第二次渲染: 新旧vnode均存在,需要在patch函数中进行更新;
  • 第三次渲染: 新的vnodenull 判断旧节点container._vnode是否存在,若旧的vnode存在,则处理为卸载;

四、如何实现一个与平台无关的渲染器:

一个通用的渲染器应该是是与平台无关的,即渲染器的渲染功能不能只在某一个平台中有效。 为了实现这个目标,我们首先来实现一个基于浏览器平台的渲染器,观察在哪些步骤中使用到了和浏览器相关的API,之后可以考虑将这些API抽离,作为配置项,这样就可以实现一个通用的渲染器。

1.实现一个依赖浏览器API的渲染器

以之前的的vnode为例:

const vnode = { 
    type: 'h1', 
    children: 'hello' 
}
  • type为标签类型、
  • children为子元素。
    在以上实现的基础上我们首先完善patch函数,用于处理节点的挂载和更新情况
patch(container._vnode, vnode, container) 

通过之前处理的代码,可以看到,patch函数会接受三个参数,分别是 旧的节点、新的节点、以及容器, 我们在其中对旧节点参数进行判断,从而处理不同的操作:目前只分析挂载的情况:

  function patch(n1, n2, container) {
    if (!n1) {
      mountElement(n2, container)
    } else {
      //
    }
  }

如上代码所示: 当n1即旧节点不存在时证明是挂载操作,则直接调用mountElement对新的节点进行挂载处理

  function mountElement(vnode, container) {
    const el = createElement(vnode.type)
    if (typeof vnode.children === 'string') {
      el.textContent = vnode.children
    }
    container.appendChild(el)
  }

mountElement中我们对挂载的逻辑进行了简单的处理:

1. 使用createElement根据vnode中的type的值去创建对应的DOM类型:vnode.type='h1'则el为<h1></h1>

2. 对vnode.children进行判断: 如果vnode.children为字符串类型则证明子节点是一个文本节点,直接使用元素的textContent属性设置元素。

3. 调用appendChild将处理好的元素添加到目标容器中。由此挂载完成。

2.实现一个通用的渲染器

以上实现的渲染器对于浏览器的API是有依赖的,其中的appendChildcreateElementtextContent都是需要浏览器环境才能执行的API,如果要让渲染器能够通用,就需要去除对这些API的依赖。

解决办法:将这些API作为配置项传入渲染器创建函数,这样我们可以自己定义createRenderer使用哪些API去执行渲染操作,从而使得渲染器的工作不再依赖于某一平台。

const renderer2 = createRenderer({
  //创建元素
  createElement(tag) {
    return { tag }
  },
  //设置元素文本内容
  setElementText(el, text) {
    console.log(`设置 ${JSON.stringify(el)} 的文本内容:${text}`)
    el.text = text
  },
  //将元素插入目标节点
  insert(el, parent, anchor = null) {
    parent.children = el
  }
})

我们将创建元素的逻辑和API放入createElement中,将设置文本内容的API放入setElementText中, 将元素挂载到容器的API过程放入insert中。 再使用传入的配置去调用mountElement

  function mountElement(vnode, container) {
    const el = createElement(vnode.type)
    if (typeof vnode.children === 'string') {
      setElementText(el, vnode.children)
    }
    insert(el, container)
  }

这样我们可以通过传入的createRenderer函数的options去配置,不同平台中使用什么样的方法去执行元素的创建、元素内容的处理以及元素挂载等操作。
由此整个渲染函数如下:

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) {
    if (!n1) {
      mountElement(n2, container)
    } else {
      //
    }
  }

  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
  }
}

由此一个简单的渲染器已经实现,但在实际情况下元素的挂载和更新还有更多的细节现需要处理,如元素上的属性、class、事件如何被正确的挂载,如何提升渲染器更新的效率(diff算法)等,这些将在后续文章中进行讨论。

以上就是一文详解Vue中渲染器的简单实现的详细内容,更多关于Vue实现渲染器的资料请关注脚本之家其它相关文章!

相关文章

  • Vue3使用Element Plus实现列表界面的方法步骤

    Vue3使用Element Plus实现列表界面的方法步骤

    写后台管理的时候会有很多列表以及相应的条件查询,下面这篇文章主要给大家介绍了关于Vue3使用Element Plus实现列表界面的方法步骤,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-04-04
  • Vue后台管理系统权限控制与动态路由的实现

    Vue后台管理系统权限控制与动态路由的实现

    本文主要介绍了Vue后台管理系统权限控制与动态路由的实现,可以根据用户的角色灵活控制页面访问权限,提高系统的安全性和用户体验,感兴趣的可以了解一下
    2025-04-04
  • vue+Element-ui实现分页效果

    vue+Element-ui实现分页效果

    这篇文章主要为大家详细介绍了vue+Element-ui实现分页效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-11-11
  • 解决VUE3 keep-alive页面切换报错parentComponent.ctx.deactivate

    解决VUE3 keep-alive页面切换报错parentComponent.ctx.deactivate

    这篇文章主要介绍了解决VUE3 keep-alive页面切换报错parentComponent.ctx.deactivate is not a function的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-10-10
  • Vue实现极致舒适的可编辑表格

    Vue实现极致舒适的可编辑表格

    使用ElementPlus的Table啥都好,就是没有可编辑表格,所以这篇文章就来和大家分享一下Vue实现极致舒适的可编辑表格的方法,希望对大家有所帮助
    2023-06-06
  • 手把手教你如何使用Vue+Django实现RBAC权限管理

    手把手教你如何使用Vue+Django实现RBAC权限管理

    在开发一个复杂的Web应用时,权限管理是绕不开的核心环节,本文将和大家一起探索并实现一个基于角色权限管理系统,感兴趣的小伙伴可以跟随小编一起学习一下
    2026-03-03
  • Vue中如何使用mock模拟数据

    Vue中如何使用mock模拟数据

    这篇文章主要介绍了Vue中如何使用mock模拟数据,具有很好的价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • Vue3 接入 i18n 实现国际化多语言案例分析

    Vue3 接入 i18n 实现国际化多语言案例分析

    在 Vue.js 3 中实现网页的国际化多语言,最常用的包是 vue-i18n,通常我们会与 vue-i18n-routing 一起使用,这篇文章主要介绍了Vue3 如何接入 i18n 实现国际化多语言,需要的朋友可以参考下
    2024-07-07
  • Vuerouter的beforeEach与afterEach钩子函数的区别

    Vuerouter的beforeEach与afterEach钩子函数的区别

    本文详细的介绍了Vuerouter的beforeEach与afterEach钩子函数的区别和使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-12-12
  • vue通过tailwindcss实现class动态绑定

    vue通过tailwindcss实现class动态绑定

    这篇文章主要介绍了vue通过tailwindcss实现class动态绑定,文中给大家介绍了一些常用类名语法记录,对vue动态绑定class相关知识感兴趣的朋友一起看看吧
    2023-07-07

最新评论