前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >6. 「vue@2.6.11 源码分析」组件渲染之虚拟DOM上界面

6. 「vue@2.6.11 源码分析」组件渲染之虚拟DOM上界面

作者头像
tinyant
发布2023-02-24 10:25:35
9310
发布2023-02-24 10:25:35
举报
  1. 首先是通过document.createElement创建该元素
  2. setScope 是为了支持 scoped CSS. 特性的。
    • 记个todo
  3. 递归创建孩子 -> createChildren,递归调用,没什么好说的。
  4. 触发create相关的钩子,并保存新创建到元素到insertVnodeQueue,后面invokeInsertHooks时会用到。(cbs.create是数组是因为会存在多个模块需要处理该元素(主体是模块),而vnode.data.hook.create只是用来处理自身的(主体是自己))
  5. 挂载或者衔接(不一定是挂载到界面上)

  • createElm逻辑如下:
image.png
image.png

createComponent

组件在更新的时候也要重新创建? 不浪费性能吗?

代码语言:js
复制
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
  let i = vnode.data
  if (isDef(i)) {
    const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
    if (isDef(i = i.hook) && isDef(i = i.init)) {
      i(vnode, false /* hydrating */)
    }
    // after calling the init hook, if the vnode is a child component
    // it should've created a child instance and mounted it. the child
    // component also has set the placeholder vnode's elm.
    // in that case we can just return the element and be done.
    if (isDef(vnode.componentInstance)) {
      initComponent(vnode, insertedVnodeQueue)
      insert(parentElm, vnode.elm, refElm)
      if (isTrue(isReactivated)) {
        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
      }
      return true
    }
  }
}
  1. 首先会调用vnode.data.hook.init,不考虑keepAlive场景下,这里调用createComponentInstanceForVnode创建子组件实例,而后调用$mount进行子组件的渲染。(和我们之前的文章new Vue() 整体流程对应上了是不是,整个过程两个大的步骤:实例初始化 + 渲染) // inline hooks to be invoked on component VNodes during patch const componentVNodeHooks = { init (vnode: VNodeWithData, hydrating: boolean): ?boolean { if (vnode.componentInstance && !vnode.componentInstance._isDestroyed && vnode.data.keepAlive) { // keepAlive 场景,暂忽略 } else { const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ) child.$mount(hydrating ? vnode.elm : undefined, hydrating) } },
代码语言:txt
复制
  //... insert、prepatch、destroy
代码语言:txt
复制
}
代码语言:txt
复制
```
  1. 上面创建完组件实例后,会将组件vue实例保存到vnode.componentInstance上,如果存在组件实例则function isPatchable (vnode) { while (vnode.componentInstance) { vnode = vnode.componentInstance._vnode } return isDef(vnode.tag) }
    • initComponent:
      1. 上面部分介绍patch的第二个场景vnode && !oldVnode会创建一个游离组件,组件等待挂载 ,还没有想到这种场景,记个TODO ❎
      2. isPatchable递归向下查找根节点不是组件标签的vnode,返回该vnode.tag,(提供一个例子,记个TODO ❎,为什么要找这个vnode);常规场景返回true即 -> invokeCreateHooks -> setScope(invokeCreateHooks里面执行vnode.data.hook.create,当前这个vnode不会执行,因为不是组件标签vnode❎,待后面补充案例验证)
      • 【待补充】 vnode、vnode.componentInstance、componentInstance._vnode之间的指向关系(我已经有点乱了)
代码语言:txt
复制
function initComponent (vnode, insertedVnodeQueue) {
代码语言:txt
复制
  if (isDef(vnode.data.pendingInsert)) {
代码语言:txt
复制
    //... 创建的游离组件情况(暂时没有挂载)
代码语言:txt
复制
  }
代码语言:txt
复制
  vnode.elm = vnode.componentInstance.$el
代码语言:txt
复制
  if (isPatchable(vnode)) {
代码语言:txt
复制
    invokeCreateHooks(vnode, insertedVnodeQueue)
代码语言:txt
复制
    setScope(vnode)
代码语言:txt
复制
  } else {
代码语言:txt
复制
    // empty component root.
代码语言:txt
复制
    // skip all element-related modules except for ref (#3455)
代码语言:txt
复制
    registerRef(vnode)
代码语言:txt
复制
    // make sure to invoke the insert hook
代码语言:txt
复制
    insertedVnodeQueue.push(vnode)
代码语言:txt
复制
  }
代码语言:txt
复制
}
代码语言:txt
复制
```
代码语言:txt
复制
- insert:document.insertBefore/appendChild
- keepAlive场景下,reactivateComponent,不展开。

  • createComponent逻辑如下:
    image.png
    image.png

patchVnode & updateChildren

这两个方法和snabbdom中的实现几乎完全一致,可以参考,下面重点说下patchVnode差异部分。

  1. 异步组件加载的处理,return
  2. 静态节点优化处理:isStatic,编译环节会给静态节点添加该标记。参考官方解释-static-hoisting,目的是对于此类节点在更新时没必要重新构造vnode然后对比。 return
  3. 关键差异,通过调用vnode.data.hook.prepatch,组件vnode的更新。// inline hooks to be invoked on component VNodes during patch const componentVNodeHooks = { prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) { const options = vnode.componentOptions const child = vnode.componentInstance = oldVnode.componentInstance updateChildComponent( child, options.propsData, // updated props options.listeners, // updated listeners vnode, // new parent vnode options.children // new children ) }, }
  4. isPatchable -> update相关的钩子(cbs.update、vnode.data.hook.update)
  5. vnode.data.hook.postpatch,搜索src/发现可能和directives有关,后面会单独章节分析指令相关 TODO ❎

这里主要区别是针对组件vnode的处理:updateChildComponent

updateChildComponent

在之前创建的组件实例中,组件vue实例是保存在vnode.componentInstance中,重新渲染组件实例并不会重新创建,还是复用之前的,但是由于属性值、事件等都可能发生了变化,因此需要更新。虽然组件实例不会重新创建,但是组件标签本身关联的vnode还是会重新创建(新的vnode),并且在_render -> componentComponent 会获取最新的componentOptions,保存到vnode.componentOptions。

因此这里就是将新的vnode.componentOptions更新到oldVnode.componentInstance中。

关注一下props,vm._props是响应式对象(initProps中增强的),这里和initProps调用validateProp之前先设置了toggleObserving(false),考虑到validateProp中的有效调用是getPropDefaultValue,难道是针对它?记个TODO ❎,有专门的提交添加此处的代码,见commit#d6bef795

注意:由于这里给vm._props重新赋值了,因此组件中computed、watch、渲染watcher等订阅的观察者都会触发。

代码语言:js
复制
export function updateChildComponent (
  vm: Component, propsData: ?Object, listeners: ?Object,
  parentVnode: MountedComponentVNode, renderChildren: ?Array<VNode>) {
 
  //... slot 相关,暂且忽略,后面可能小节分析

  vm.$options._parentVnode = parentVnode
  vm.$vnode = parentVnode // update vm's placeholder node without re-render

  if (vm._vnode) { // update child tree's parent
    vm._vnode.parent = parentVnode
  }
  vm.$options._renderChildren = renderChildren

  // update $attrs and $listeners hash
  // these are also reactive so they may trigger child update if the child
  // used them during render
  vm.$attrs = parentVnode.data.attrs || emptyObject
  vm.$listeners = listeners || emptyObject

  // update props
  if (propsData &amp;&amp; vm.$options.props) {
    toggleObserving(false)
    const props = vm._props
    const propKeys = vm.$options._propKeys || []
    for (let i = 0; i < propKeys.length; i++) {
      const key = propKeys[i]
      const propOptions: any = vm.$options.props // wtf flow?
      props[key] = validateProp(key, propOptions, propsData, vm)
    }
    toggleObserving(true)
    // keep a copy of raw propsData
    vm.$options.propsData = propsData
  }

  // update listeners
  listeners = listeners || emptyObject
  const oldListeners = vm.$options._parentListeners
  vm.$options._parentListeners = listeners
  updateComponentListeners(vm, listeners, oldListeners)

  //... slot相关 暂且忽略

}

总结

先发一版,后面会以一个有父子组件的demo,并记录执行状态,来更新第五节和本节的内容,并给出更多图示,比如父子组件实例,vnode的相互引用关系。

代码语言:txt
复制
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-02-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • vm._update
  • patch
    • 1. !vnode && oldVnode(vnode不存在但是oldVnode存在)
      • invokeDestroyHook
      • vm.$destroy()
      • deactivateChildComponent
      • 小结
    • 2. vnode && !oldVnode(vnode存在 并且 oldVnode不存在)
      • invokeInsertHook
    • 3. vnode && oldVnode(二者均存在)
      • 3.1 oldVnode不是DOM && sameVnode(oldVnode, vnode)
      • 3.2 oldVnode是DOM
    • removeVnodes -> xxxInvokeRemoveHook
      • 小结
      • createElm
        • createComponent
        • patchVnode & updateChildren
          • updateChildComponent
          • 总结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档