|虚拟DOM(上):如何通过虚拟DOM更新页面?

你好,我是大圣。

上一讲我们主要介绍了Vue项目的首次渲染流程,在mountComponent中注册了effect函数,这样,在组件数据有更新的时候,就会通知到组件的update方法进行更新。

Vue中组件更新的方式也是使用了响应式+虚拟DOM的方式,这个我们在第一讲中有介绍过Vue 1、Vue 2和Vue 3中更新方式的变化,今天我们就来详细剖析一下Vue组件内部如何通过虚拟DOM更新页面的代码细节。

Vue虚拟DOM执行流程

我们从虚拟DOM在Vue的执行流程开始讲起。在Vue中,我们使用虚拟DOM来描述页面的组件,比如下面的template虽然格式和HTML很像,但是在Vue的内部会解析成JavaScript函数,这个函数就是用来返回虚拟DOM:

< div id = "app" >
  <
  p > hello world < /p> <
  Rate: value = "4" > < /Rate> <
  /div>

上面的template会解析成下面的函数,最终返回一个JavaScript的对象能够描述这段HTML:

function render() {
  return h('div', {
    id: "app"
  }, children: [
    h('p', {}, 'hello world'),
    h(Rate, {
      value: 4
    }),
  ])
}

知道虚拟DOM是什么之后,那么它是怎么创建的呢?

DOM的创建

我们简单回忆上一讲介绍的mount函数,在代码中,我们使用createVNode函数创建项目的虚拟DOM,可以看到Vue内部的虚拟DOM,也就是vnode,就是一个对象,通过type、props、children等属性描述整个节点

const vnode = createVNode(
  rootComponent as ConcreteComponent,
  rootProps
)

function\ _createVNode() {

  // 处理属性和class
  if (props) {
    ...
  }

  // 标记vnode信息
  const shapeFlag = isString(type) ?
    ShapeFlags.ELEMENT :
    \_\ _FEATURE\ _SUSPENSE\ _\ _ && isSuspense(type) ?
    ShapeFlags.SUSPENSE :
    isTeleport(type) ?
    ShapeFlags.TELEPORT :
    isObject(type) ?
    ShapeFlags.STATEFUL\ _COMPONENT :
    isFunction(type) ?
    ShapeFlags.FUNCTIONAL\ _COMPONENT :
    0

  return createBaseVNode(
    type,
    props,
    children,
    patchFlag,
    dynamicProps,
    shapeFlag,
    isBlockNode,
    true
  )
}

function createBaseVNode(type, props, children, ...) {
  const vnode = {
    type,
    props,
    key: props && normalizeKey(props),
    ref: props && normalizeRef(props),
    children,
    shapeFlag,
    patchFlag,
    dynamicProps,
    ...
  }
  as VNode
  // 标准化子节点
  if (needFullChildrenNormalization) {
    normalizeChildren(vnode, children)
  } else if (children) {
    vnode.shapeFlag |= isString(children) ?
      ShapeFlags.TEXT\ _CHILDREN :
      ShapeFlags.ARRAY\ _CHILDREN
  }
  return vnode
}
componentUpdateFn

createVNode负责创建Vue中的虚拟DOM,而上一讲中我们讲过mount函数的核心逻辑就是使用setupComponent执行我们写的