精读《Vuejs设计与实现》(9)之初识渲染器

168 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情

3.2初识渲染器

现在我们大概能了解什么虚拟dom,它其实就是用对象来描述真实dom。这个对象的结构你可以自己设计,因为虚拟dom需要通过渲染器转化为真实dom,你只需要再实现一个对应你结构的渲染器即可,这也是本章的主要内容。、

之所以叫初识,因为渲染器如果专门来讲,可以讲上一天一夜,它还涉及到diff算法等,这一张只是大概的讲一下,后续会有专门章节。

image.png 这就是渲染器的基本功能,渲染器其实是很重要的功能,大部分时候,我们说起vue的虚拟dom,其实就包含虚拟dom结构设计和渲染器。

其实当你了解虚拟dom和渲染器之后,你可以自己写一些demo,实现一个简单的虚拟dom系统,假设我们现在有这样一个虚拟dom结构,我们约定,以on开头的都是事件,tag、children是渲染器用的,其他都是dom的属性

const vdom = {
  tag:'button',
  id:'button',
  className:'div1',
  children:'click Me!!!',
  onClick(){
    alert('hello world')
  }
}

那么我们开始实现一下这个渲染器

const render = (vnode,container)=>{
  //不存在tag就报错
  if(!vnode.tag){
    throw new Error('tag is required')
  }
  // 创建真实dom
  const realDom = document.createElement(vnode.tag)
  // 过滤一些内部用的keyName
  const keys = Object.keys(vnode).filter(e=>!['tag','children'].includes(e))
  //这里是dom的属性
  const props = keys.filter(e=>!e.startsWith('on'))
  //这里是事件
  const events = keys.filter(e=>e.startsWith('on'))
  //调用setAttribute设置dom的属性,这样更安全
  props.forEach(item=>{
    realDom.setAttribute(item,vnode[item])
  })
  //加入事件监听
  events.forEach(item=>{
    if(typeof vnode[item] === 'function'){
      //转换时间名,onClick->click
      const eventName = item.replace('on','').toLowerCase()
      realDom.addEventListener(eventName,vnode[item])
    }
  })
  if(vnode.children){
    //如果children仅是字符串
      if(typeof vnode.children === 'string'){
          const textNode = document.createTextNode(vnode.children)
          realDom.appendChild(textNode)
      } else if(Array.isArray(vnode.children)) {
          // 递归children
        vnode.children.forEach(child=>render(child,realDom))
      } else{
        throw new Error('invalid children')
      }
  }
  
  //判断一下container是字符串还是dom,然后插入dom
  if(typeof container === 'string'){
    document.querySelector(container)?.appendChild(realDom)
  } else {
    container.appendChild(realDom)
  }
}

调用一下这个函数,render(vdom,'body')就会产生这个 image.png

点击按钮,发现click事件也加上 image.png

这就是一个非常非常简单的渲染器。总提来说,实现这个渲染器主要分为4步

  1. 以vdom.tag创建真实dom 2.处理属性与事件:在这个例子里,我把虚拟dom的格式扁平化了,通过on来判断是否是事件,然后分别处理。其实你也可以把事件写到on里,把属性写到props里.这样就好基本上是vue2的虚拟dom类型,这样更容易写判断。
const vdom = {
    tag:'button',
    on:{
       click(){}
    },
    props:{}
} 

3.处理children:这一步其实很关键,因为很多时候,我们的children可能有字符串,数组套vdom,甚至会有多层children,这里都需要处理,还有很多边界条件,我这里只是简单的写了一下。 4.插入真实dom

但是这样是不够的,这样的渲染器只能渲染固定的内容,每次都会插入,我们知道在实际使用中,不但有创建,更重要的是更新。

const vdom = {
    tag:'button',
    children:'click Me!!!',
} 
render(vdom,'body')
setTimeout(()=>{
    vdom.children = 'click Again'
    render(vdom,'body')
},1000)

如果render函数里不做处理,你会发现它有渲染了一个新的按钮,这明显是不对的。这一块就是diff的过程了,书里后续会专门讲解。