详解Vue如何手写虚拟dom并进行渲染

 更新时间:2024年03月11日 14:49:26   作者:不要止步于此  
这篇文章主要为大家详细介绍了渲染器的工作原理,以及如何将真实dom或者组件用虚拟dom的形式进行描述并渲染,感兴趣的小伙伴可以跟随小编一起学习一下

虚拟dom如何渲染为真实dom

虚拟dom转换为真实dom其实就是编写一个渲染函数,将虚拟dom逐个创建为真实的dom元素并将对应事件添加到当前创建的元素上,再挂载到页面的指定元素下。

认识虚拟dom

虚拟dom其实就是用来描述真实dom的javascript对象。

来看下面例子,把一个真实dom用虚拟dom来进行描述:

真实dom

<div onclick="onAlert()"><span>点击我</span><span></span></div>
<script>
    function onAlert() {
      alert('点击事件回调函数')
    }
</script>

转换为虚拟dom

const vnode = {
  tag: 'div',
  props: {
    onClick: () => alert('点击事件回调函数')
  },
  children: [
    {
      tag: 'span',
      children: '点击我'
    },
    {
      tag: 'span',
    }
  ]
}

渲染器实现

/**
 * @param {object} vnode 虚拟dom对象
 * @param {HTMLElement} container 挂载虚拟dom的真实dom容器
 */
function renderer(vnode, container) {
  const { tag, props, children } = vnode
  const el = document.createElement(tag)
  for(const key in props) {
    if(/^on/.test(key)) {
      // 转换为合法的监听事件名称
      const eventNmae = key.substring(2).toLowerCase()
      // 在当前创建的el元素上挂载监听事件
      el.addEventListener(eventNmae, props[key])
    }
  }

  if(typeof children === 'string') {
    // 创建一个文本节点添加到el元素下
    el.appendChild(document.createTextNode(children))
  } else if(Array.isArray(children)) {
    // 子节点为数组,递归调用renderer函数
    children.forEach(vnode => renderer(vnode, el))
  }
  console.log(container)
  // 将元素挂载到容器上
  container.appendChild(el)
}

现在将上面转换的虚拟dom传入函数执行看下效果

// 把虚拟dom渲染到id为app的元素下
renderer(vnode, document.getElementById('app'))

下图可看到虚拟dom已经成功渲染为真实dom,并且点击事件也成功触发了!

虚拟dom描述组件

以上讲了如何使用虚拟dom(vnode)描述真实dom,但还不够!如果我们封装了一个组件,又该如何使用虚拟dom进行描述呢?

总的来说,组件就是一组dom元素的封装,这组dom元素就是组件要渲染的内容,比如前面例子的vnode对象就可以认为是一个组件。

方法组件

const MyComponent = function () {
  return {
    tag: 'div',
    props: {
      onClick: () => alert('MyComponent点击事件回调函数')
    },
    children: [
      {
        tag: 'span',
        children: 'MyComponent'
      },
      {
        tag: 'span',
      }
    ]
  }
}

对象组件

const MyComponent2 = {
  render() {
    return {
      tag: 'div',
      props: {
        onClick: () => alert('MyComponent2点击事件回调函数')
      },
      children: [
        {
          tag: 'span',
          children: 'MyComponent2'
        },
        {
          tag: 'span',
        }
      ]
    }
  }
}

修改渲染器支持组件渲染

/**
 * @param {object} vnode 虚拟dom对象
 * @param {HTMLElement} container 挂载虚拟dom的真实dom容器
 */
function renderer(vnode, container) {
  const { tag } = vnode
  if(typeof tag === 'string') {
    mountElement(vnode, container)
  } else if(typeof tag === 'function') {
    mountComponent(tag(), container)
  } else if(typeof tag === 'object') {
    mountComponent(tag.render(), container)
  }
}

function mountElement(vnode, container) {
  const { tag, props, children } = vnode
  const el = document.createElement(tag)
  for(const key in props) {
    if(/^on/.test(key)) {
      // 转换为合法的监听事件名称
      const eventNmae = key.substring(2).toLowerCase()
      // 在当前创建的el元素上挂载监听事件
      el.addEventListener(eventNmae, props[key])
    }
  }

  if(typeof children === 'string') {
    // 创建一个文本节点添加到el元素下
    el.appendChild(document.createTextNode(children))
  } else if(Array.isArray(children)) {
    // 子节点为数组,递归调用renderer函数
    children.forEach(vnode => renderer(vnode, el))
  }
  console.log(container)
  // 将元素挂载到容器上
  container.appendChild(el)
}

function mountComponent(vnode, container) {
  // 递归调用renderer
  renderer(vnode, container)
}

渲染组件

const vnode = {
  tag: 'div',
  children: [
    {
      tag: 'span',
      props: {
        onClick: () => alert('span点击事件回调函数')
      },
      children: '我是span标签'
    },
    // 组件
    {
      tag: MyComponent,
    },
    // 组件
    {
      tag: MyComponent2,
    }
  ]
}

// 把虚拟dom渲染到id为app的元素下
renderer(vnode, document.getElementById('app'))

下图可看到,对应的组件及事件都已经挂载成功!

总结

最后,是不是觉得渲染器其实也没有想象中那么难!其实这只是一个创建节点的渲染器,但其精髓在于更新节点。假设我们对虚拟dom做了一些小修改,渲染器需要精确找到vnode对象的变更点且只更新变更的内容,而不是重新走一遍创建节点的流程。

以上就是详解Vue如何手写虚拟dom并进行渲染的详细内容,更多关于Vue虚拟dom的资料请关注脚本之家其它相关文章!

相关文章

  • VUE前端实现token的无感刷新3种方案(refresh_token)

    VUE前端实现token的无感刷新3种方案(refresh_token)

    这篇文章主要给大家介绍了关于VUE前端实现token的无感刷新3种方案(refresh_token)的相关资料,为了提供更好的用户体验,我们可以通过实现Token的无感刷新机制来避免用户在使用过程中的中断,需要的朋友可以参考下
    2023-11-11
  • 详解利用eventemitter2实现Vue组件通信

    详解利用eventemitter2实现Vue组件通信

    这篇文章主要介绍了详解利用eventemitter2实现Vue组件通信,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-11-11
  • Vue封装组件并上传到npm的教程详解

    Vue封装组件并上传到npm的教程详解

    这篇文章主要为大家详细介绍了Vue封装组件并上传到npm的相关教程,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以参考下
    2024-04-04
  • vue双向绑定数据限制长度的方法

    vue双向绑定数据限制长度的方法

    这篇文章主要为大家详细介绍了vue双向绑定数据限制长度的方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-11-11
  • 在vue中读取本地Json文件的方法

    在vue中读取本地Json文件的方法

    今天小编就为大家分享一篇在vue中读取本地Json文件的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-09-09
  • 如何手写简易的 Vue Router

    如何手写简易的 Vue Router

    这篇文章主要介绍了如何手写简易的 Vue Router,帮助大家更好的理解和使用vue,感兴趣的朋友可以了解下
    2020-10-10
  • vue3父子同信的双向数据的项目实现

    vue3父子同信的双向数据的项目实现

    我们知道的是,父传子的通信,和子传父的通信,那如何实现父子相互通信的呢,本文就来详细的介绍一下,感兴趣的可以了解一下
    2023-08-08
  • VUE引入腾讯地图并实现轨迹动画的详细步骤

    VUE引入腾讯地图并实现轨迹动画的详细步骤

    这篇文章主要介绍了VUE引入腾讯地图并实现轨迹动画,引入步骤大概是在 html 中通过引入 script 标签加载API服务,结合实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-09-09
  • vue 中自定义指令改变data中的值

    vue 中自定义指令改变data中的值

    这篇文章主要介绍了vue 中自定义指令改变data中的值,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2017-06-06
  • vue.js+element-ui动态配置菜单的实例

    vue.js+element-ui动态配置菜单的实例

    今天小编就为大家分享一篇vue.js+element-ui动态配置菜单的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-09-09

最新评论