Vue源码之简易渲染器renderer实现

1.虚拟DOM的优势

前端三大框架引入虚拟DOM来对真实DOM进行抽象,这样做的好处:

  • 直接进行DOM操作是很多限制的,使用虚拟DOM将元素节点变成JS对象,我们可以操作更多的逻辑,比如:diff、clone等。
  • 跨平台性:可以将虚拟节点Vnode以你想要的形式渲染到不同的平台上,Vue是支持你开发属于自己的渲染器的。

2.虚拟DOM的渲染过程

Vue源码之简易渲染器renderer实现

Vue内部将template通过render函数编译成虚拟节点,虚拟节点的children如果是数组(存放虚拟节点)则是虚拟DOM树,最后挂载到真实DOM上。

3.renderer渲染器实现

3.1 生成虚拟节点

// 虚拟节点的本质通过h函数生成一个JS对象

const h = (tag, props, children) => {
  return {
   tag,
   props,
   children
  }
}

3.2 将虚拟节点挂载到真实DOM上

// 挂载mount函数
const mount = (vnode, container) => {
 // vnode中三个参数:tag,props,children
    //1.处理tag,创建html元素
    const el = document.createElement(vnode.tag)
    //保留一份el在vnode中,后续patch更新函数中会使用到
    vnode.el = el
    //2.处理props: 分为普通属性和事件函数
    for (const key in vnode.props) {
      const value = vnode.props[key]
    if (key.startWith('on')) {
      el.addEventListener(key.slice(2).toLowerCase(), value)
     } else {
   el.setAttribute(key, value) 
     }
    }
    //3.处理children
    if (vnode.children) {
     if (typeof vnode.children === 'string') {
        el.textContent = vnode.children
      } else {
        vnode.children.forEach(item => mount(item, el))
      }
    }
    //4.把vnode挂载到真实DOM上
    container.appendChild(el)
}

3.3 更新操作patch函数

//对比新旧虚拟节点,进行真实DOM更新
const patch = (n1, n2) => {
  if (n1.tag !== n2.tag) {
    //1.对比tag类型
    //tag不同直接把旧节点移除,挂载新节点
    const n1ElParent = n1.el.parenElement
    n1ElParent.removeChild(n1.el)
    mount(n2, elParent)
  } else {
 //2.tag相同处理props
    const el = n1.el
    n2.el = el
      
    const oldProps = n1.props || {}
    const newProps = n2.props || {}
    //2.1新增props属性
    for (const key in newProps) {
    if (newProps[key] !== oldProps[key]) {
        //判断属性类型:函数或是普通属性
        if (key.startWith('on')) {
     el.addEventListener(key.slice(2).toLowerCase(), newProps[key])
        } else {
     el.setAttribute(key, newProps[key])
        }
      }
    }
    //2.2删除props属性
    for (const key in oldProps) {
      //2.2.1把事件直接移除掉:因为每次patch后事件的value为function函数(引用类型),value不会一样
    if (key.startWith('on')) {
        el.removeEventListener(key.slice(2).toLowerCase(), oldProps[key])
      }
      //2.2.2把key不在newProps中的移除掉
      if (!(key in newProps)) {
   el.removeAttribute(key)
      }
    }
      
    //3.处理children
    const oldChildren = n1.children || []
    const newChildren = n2.children || []
    if (typeof newChildren === 'string') {
      if(typeof oldChildren === 'string') {
   el.textContent = newChildren
      } else {
        el.innerHTML = ''
        el.textContent = newChildren
      }
    } else {
      if (typeof oldChildren === 'string') {
   el.textContent = ''
        newChildren.forEach(item => mount(item, el))
      } else {
        const commonLength = Math.min(oldChildren.length, newChildren.length)
        for (let i = 0; i < commonLength; i++) {
          patch(oldChildren[i], newChildren[i])
        }
 
        //3.1 oldChildren.length > newChildren.length: 移除多余的虚拟节点
        if (oldChildren.length > newChildren.length) {
          //源码中采用的是unmount方法进行卸载,这里直接移除元素
     oldChildren.length.slice(commonLength).forEach(item => el.removeChild(item.el))
        }
        
        //oldChildren.length < newChildren.length: 新增新节点
        if (oldChildren.length < newChildren.length) {
     newChildren.length.slice(commonLength).forEach(item => mount(item, el))
        }
      }
    }
  }
}

4.测试效果

  <div id="app"></div>
  <script src="./renderer.js"></script>
  <script>
    // 1.通过h函数来创建一个vnode
    const vnode = h('div', {class: "title"}, [
      h('h2', null, "你好啊,李银河"),
      h('button', null, "+1"),
      h('h3', null, 'hello')
    ])  //树结构:相当于一个vdom

    // 2.通过mount函数将vnode挂载到真实dom上
    mount(vnode, document.querySelector("#app"))

    // 3.patch算法:对比虚拟节点更新dom
    setTimeout(() => {
      const vnode1 = h('div', {class: "pm-title"}, [
      h('h2', null, "hello world"),
      h('button', null, "--1")
    ])
      patch(vnode, vnode1)
    }, 2000)
  </script>
  • 初次渲染:

Vue源码之简易渲染器renderer实现

  • 2秒后:

Vue源码之简易渲染器renderer实现

–灵感来源coderwhy

继续记录前端菜狗的成长!

原文始发于微信公众号(Hephaestuses):Vue源码之简易渲染器renderer实现

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/45041.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!