前言

大家好,今日新开了个vue3规划与完成专栏,期望能够把自己在这本书中阅读到的知识结合自己的见地,一点点共享给大家,期望自己在进步的同时,也能够和大家共同进步!不积跬步无以至千里,不积小流无以成江海。本文旨在了解烘托器的工作原理,以及怎么将实在dom或者组件虚拟dom的形式进行描绘并烘托。

关键词: 烘托器、虚拟dom、实在dom

虚拟dom怎么烘托为实在dom

vue3规划与完成(一):快速手写虚拟dom烘托函数(烘托器)

虚拟dom转换为实在dom其实就是编写一个烘托函数,将虚拟dom逐一创立为实在的dom元素并将对应事情添加到当时创立的元素上,再挂载到页面的指定元素下。

认识虚拟dom

虚拟dom其实就是用来描绘实在domjavascript目标

来看下面比如,把一个实在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,并且点击事情也成功触发了!

vue3规划与完成(一):快速手写虚拟dom烘托函数(烘托器)

虚拟dom描绘组件

以上讲了怎么使用虚拟dom(vnode)描绘实在dom,但还不够!假如咱们封装了一个组件,又该怎么使用虚拟dom进行描绘呢?

vue3规划与完成(一):快速手写虚拟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'))

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

vue3规划与完成(一):快速手写虚拟dom烘托函数(烘托器)

总结

最后,是不是觉得烘托器其实也没有想象中那么难!其实这仅仅一个创立节点的烘托器,但其精髓在于更新节点。假定咱们对虚拟dom做了一些小修正,烘托器需求准确找到vnode目标的变更点且只更新变更的内容,而不是从头走一遍创立节点的流程。更新虚拟dom部分后边有时间再进行单独写一篇文章进行解说,假如对你有帮助可以先加一波重视哦~