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

我心飞翔 分类:vue

new Vue(..)之后总共有两个大的步骤,第一步是调用vm._init完成组件的各种准备(初始化)工作,然后是开始结合数据与模板实现页面的渲染。vue引入了虚拟DOM技术,这里页面渲染分为两步,将模板和数据(转为了render函数)转为虚拟DOM树,而后再将虚拟DOM树同步到界面上。上一小节已经分析过创建虚拟DOM树的过程,现在我们来看看虚拟DOM是如何同步到界面上的。

updateComponent = () => {
  vm._update(vm._render(), hydrating) // hydrating: ssr相关 忽略
}

new Watcher(vm, updateComponent, noop, {
  before () { /*...*/ }
}, true /* isRenderWatcher */)

vm._update

export function lifecycleMixin (Vue: Class<Component>) {
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    if (!prevVnode) {
      // initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    restoreActiveInstance()
    // update __vue__ reference
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    // if parent is an HOC, update its $el as well
    if (vm.$vnode &amp;&amp; vm.$parent &amp;&amp; vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }
 
  //...
}
  1. 新vnode来自上一步_render刚创建的虚拟DOM树,preVnode则是上一次创建的虚拟DOM树
  2. 然后就是最关键的步骤:patch,对比新老虚拟DOM,找出差异,同步到界面上
    • patch方法会返回一个DOM,然后保存到$el上,组件就和DOM关联起来了
  3. el.__vue__,DOM关联组件实例 结合第三步,DOM 和 vm 相互引用 HOC场景的 parent.el 更新?
  4. 组件的渲染是通过scheduler进行调度的。udpated hook,先父后子(flushSchedulerQueue -> callUpdatedHooks)❓❓❓
    • 目的是?

下面重点看下patch方法

patch

这里的核心逻辑在snabbdom源码分析中说过,参考snabbdom@3.5.1 源码分析第三篇。

export function createPatchFunction (backend) {
    //...
    return function patch (oldVnode, vnode, hydrating, removeOnly) {
      if (isUndef(vnode)) {
        if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
        return
      }

      //...
      if (isUndef(oldVnode)) {
        //...createElm
      } else {
        const isRealElement = isDef(oldVnode.nodeType)
        if (!isRealElement &amp;&amp; sameVnode(oldVnode, vnode)) {
          //... patchVnode
        } else {
          //... createElm
        }
      }

      invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
      return vnode.elm
    }
}

总共分为四种情况,见下面分析。

1. !vnode && oldVnode(vnode不存在但是oldVnode存在)

直接调用invokeDestroyHook触发oldVnode销毁逻辑

invokeDestroyHook

function invokeDestroyHook (vnode) {
    let i, j
    const data = vnode.data
    if (isDef(data)) {
      if (isDef(i = data.hook) &amp;&amp; isDef(i = i.destroy)) i(vnode)
      for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)
    }
    if (isDef(i = vnode.children)) {
      for (j = 0; j < vnode.children.length; ++j) {
        invokeDestroyHook(vnode.children[j])
      }
    }
  }
const componentVNodeHooks = {
    //... init、insert、prepatch

    destroy (vnode: MountedComponentVNode) {
      const { componentInstance } = vnode
      if (!componentInstance._isDestroyed) {
        if (!vnode.data.keepAlive) {
          componentInstance.$destroy()
        } else {
          deactivateChildComponent(componentInstance, true /* direct */)
        }
      }
    }
}

vm.$destroy()

  1. 已经销毁则返回
  2. 触发生命周期:beforeDestroy,并设置正在销毁_isBeingDestroyed标识
  3. 取消父子组件关系(npare.$children中移除当前删除的组件实例vm)
  4. watcher销毁
    1. 销毁渲染关联的watcher(在mountComponent创建的,用来渲染组件的)
    2. 销毁组件中开发者提供watch属性生成的watchers(在initState -> initWatcher中创建的)
  5. 设置已经销毁标识_isDestroyed
  6. 移除vm._vnode,同样是通过patch去处理,通过和null进行diff,来移除(和snabbdom几乎一致的思路)
  7. 触发 destroyed 声明周期
  8. 取消所有的事件监听
  9. 取消关联的DOM指向js对象的引用,vm.$el.__vue__ = null
    • 否则 DOM删除不了??需要验证下,参考
  10. vm.$vnode.parent = null,keep-alive场景下的内存泄漏:issue#6759

结论:需要清理一切需要清理的,并且所有的属性应该都是统一在一个地方声明,确保删除的时候没有遗漏。

  Vue.prototype.$destroy = function () {
    const vm: Component = this
    if (vm._isBeingDestroyed) {
      return
    }
    callHook(vm, 'beforeDestroy')
    vm._isBeingDestroyed = true
    // remove self from parent
    const parent = vm.$parent
    if (parent &amp;&amp; !parent._isBeingDestroyed &amp;&amp; !vm.$options.abstract) {
      remove(parent.$children, vm)
    }
    // teardown watchers
    if (vm._watcher) {
      vm._watcher.teardown()
    }
    let i = vm._watchers.length
    while (i--) {
      vm._watchers[i].teardown()
    }
    // remove reference from data ob
    // frozen object may not have observer.
    if (vm._data.__ob__) {
      vm._data.__ob__.vmCount--
    }
    // call the last hook...
    vm._isDestroyed = true
    // invoke destroy hooks on current rendered tree
    vm.__patch__(vm._vnode, null)
    // fire destroyed hook
    callHook(vm, 'destroyed')
    // turn off all instance listeners.
    vm.$off()
    // remove __vue__ reference
    if (vm.$el) {
      vm.$el.__vue__ = null
    }
    // release circular reference (#6759)
    if (vm.$vnode) {
      vm.$vnode.parent = null
    }
  }

deactivateChildComponent

keepAlive场景下,看到这里涉及 _inactive 的使用

  • keep-alive后面可能单独小节说下。❓❓❓
function deactivateChildComponent (vm, direct) {
  if (direct) {
    vm._directInactive = true;
    if (isInInactiveTree(vm)) {
      return
    }
  }
  if (!vm._inactive) {
    vm._inactive = true;
    for (var i = 0; i < vm.$children.length; i++) {
      deactivateChildComponent(vm.$children[i]);
    }
    callHook(vm, 'deactivated');
  }
}

小结

.
  • vm.$destroy:组件销毁包括DOM移除、事件和watcher等移除、触发beforeDestroy、destroyed生命周期等。
  • deactivateChildComponent(keep-alive场景):❓❓❓
  • cbs.destroy收集注册的module的对应钩子,即在调用createPatchFunction传递的modules参数(如果你看过snabbdom源码分析,就会知道他们的作用啦)。

2. vnode && !oldVnode(vnode存在 并且 oldVnode不存在)

这种情况暂时不会真正挂载界面上,因为没有提供挂载点(肯定需要oldVnode存在的)

这种情况就两个步骤

// empty mount (likely as component), create new root element
isInitialPatch = true // 关键
createElm(vnode, insertedVnodeQueue) // 注意:没有提供挂载点:parentElm

// isInitialPatch为true时,会延迟insert hooks执行,指导真正挂载到界面上。
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) // isInitialPatch: true
  • createElm 非常重要下面会重点分析,其作用是创建DOM并挂载。
  • invokeInsertHook 逻辑如下

invokeInsertHook

function invokeInsertHook (vnode, queue, initial) {
  // delay insert hooks for component root nodes, invoke them after the
  // element is really inserted
  if (isTrue(initial) &amp;&amp; isDef(vnode.parent)) {
    vnode.parent.data.pendingInsert = queue
  } else {
    for (let i = 0; i < queue.length; ++i) {
      queue[i].data.hook.insert(queue[i])
    }
  }
}
  1. 注意isInitialPatch情况,延迟当前创建的root component的insert hooks。
  2. 非上面场景,即会挂载到界面上,执行vnode.data.hook.insert

其中vnode.data.hook.insert如下:

const componentVNodeHooks = {

    insert (vnode: MountedComponentVNode) {
      const { context, componentInstance } = vnode
      if (!componentInstance._isMounted) {
        componentInstance._isMounted = true
        callHook(componentInstance, 'mounted')
      }
      if (vnode.data.keepAlive) {
        if (context._isMounted) {
          // vue-router#1212
          // During updates, a kept-alive component's child components may
          // change, so directly walking the tree here may call activated hooks
          // on incorrect children. Instead we push them into a queue which will
          // be processed after the whole patch process ended.
          queueActivatedComponent(componentInstance)
        } else {
          activateChildComponent(componentInstance, true /* direct */)
        }
      }
    },

  
    //...
}
  • 如果组件已经没有挂载过,初次挂载,则触发mounted生命周期,并设置_isMounted标识组件已经挂载。
  • 否则:针对keepAlive场景,暂不考虑,后面可能单独小节会说。
.

3. vnode && oldVnode(二者均存在)

3.1 oldVnode不是DOM && sameVnode(oldVnode, vnode)

// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) // isInitialPatch: false

sameVnode:没什么好说的,看到针对异步加载情况单独加了判断。

function sameVnode (a, b) {
  return (
    a.key === b.key &amp;&amp; (
      (
        a.tag === b.tag &amp;&amp;
        a.isComment === b.isComment &amp;&amp;
        isDef(a.data) === isDef(b.data) &amp;&amp;
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &amp;&amp;
        a.asyncFactory === b.asyncFactory &amp;&amp;
        isUndef(b.asyncFactory.error)
      )
    )
  )
}

patchVnode很重要,后面会单独分析,其目的就是对两个vnode进行对比

3.2 oldVnode是DOM

```js if (isRealElement) { // ... ssr 相关 // create an empty node and replace it oldVnode = emptyNodeAt(oldVnode) }

// replacing existing element const oldElm = oldVnode.elm const parentElm = nodeOps.parentNode(oldElm)

// create new node // 第三个参数:issue##4590 createElm(vnode, insertedVnodeQueue, oldElm._leaveCb ? null : parentElm, nodeOps.nextSibling(oldElm))

// update parent placeholder node element, recursively // ...

// destroy old node if (isDef(parentElm)) { removeVnodes([oldVnode], 0, 0) } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode) }

1. 根据oldVnode(这个场景中,此时是一个DOM)创建一个关联的oldVnode(不会创建孩子的虚拟DOM)
2. createELm,根据vnode树创建DOM树,并挂载到界面上
3. update parent placeholder node element, recursively
- [ ] 记个TODO,vnode.parent存在表明是中间组件不是根组件,而后oldVnode还是个DOM,感觉是特殊用法。
- [ ] 两个issue:暂时忽略 [issue#6718](https://github.com/vuejs/vue/pull/6718)、[issue#6513](https://github.com/vuejs/vue/issues/6513)
4. 移除节点:如果有父节点,则调用`removeVnodes`删除oldVnode.elm,否则直接调用`invokeDestroyHook`
下面单独说一下 removeVnodes
## removeVnodes -> xxxInvokeRemoveHook
1. 如果是文本节点,直接removeNode
2. 如果存在tag(可能内置标签如`div`,也可能是组件(如`todo-item`))
- removeAndInvokeRemoveHook 用来触发remove相关的钩子、递归处理子组件、删除当前元素`rm()`
- invokeDestroyHook 触发destroy相关的的钩子
```js
function removeVnodes (vnodes, startIdx, endIdx) {
for (; startIdx <= endIdx; ++startIdx) {
const ch = vnodes[startIdx]
if (isDef(ch)) {
if (isDef(ch.tag)) {
removeAndInvokeRemoveHook(ch)
invokeDestroyHook(ch)
} else { // Text node
removeNode(ch.elm)
}
}
}
}
```
removeAndInvokeRemoveHook区分了普通节点和组件节点,如果是普通节点如`div`则直接removeNode移除就好,如果是组件节点(在创建虚拟DOM章节说过,组件标签本身也有关联的虚拟DOM,这里的组件节点就是这个虚拟DOM,并不代表组件的实际渲染内容)则需要触发组件的remove相关的钩子并且递归删除组件的实际内容(实际上就是组件实际渲染内容的根节点,`vm._vnode`,vm._render方法中执行`render`函数创建组件的虚拟DOM树,并将根节点保存到`vm._vnode`。)
```js
function removeAndInvokeRemoveHook (vnode, rm) {
if (isDef(rm) || isDef(vnode.data)) {
let i
const listeners = cbs.remove.length + 1
if (isDef(rm)) {
// we have a recursively passed down rm callback
// increase the listeners count
rm.listeners += listeners
} else {
// directly removing
rm = createRmCb(vnode.elm, listeners)
}
// recursively invoke hooks on child component root node
if (isDef(i = vnode.componentInstance) &amp;&amp; isDef(i = i._vnode) &amp;&amp; isDef(i.data)) {
removeAndInvokeRemoveHook(i, rm)
}
for (i = 0; i < cbs.remove.length; ++i) {
cbs.remove[i](vnode, rm)
}
if (isDef(i = vnode.data.hook) &amp;&amp; isDef(i = i.remove)) {
i(vnode, rm)
} else {
rm()
}
} else {
removeNode(vnode.elm)
}
}
```
## 小结
![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3520bcf4ab564c67af31b615b2fc47af~tplv-k3u1fbpfcp-watermark.image?)
# createElm
```js
function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {
if (isDef(vnode.elm) &amp;&amp; isDef(ownerArray)) {
// This vnode was used in a previous render!
// now it's used as a new node, overwriting its elm would cause
// potential patch errors down the road when it's used as an insertion
// reference node. Instead, we clone the node on-demand before creating
// associated DOM element for it.
vnode = ownerArray[index] = cloneVNode(vnode)
}
vnode.isRootInsert = !nested // for transition enter check
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
if (isDef(tag)) { 
vnode.elm = nodeOps.createElement(tag, vnode)
setScope(vnode)
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
} else if (isTrue(vnode.isComment)) {
vnode.elm = nodeOps.createComment(vnode.text)
insert(parentElm, vnode.elm, refElm)
} else {
vnode.elm = nodeOps.createTextNode(vnode.text)
insert(parentElm, vnode.elm, refElm)
}
}
```
1. 为什么需要克隆vnode❓❓❓
2. 关键:尝试创建子组件实例并渲染子组件,createComponent返回true,说明当前vnode关联的是一个组件,否则进入后面逻辑。
3. 创建元素并挂载,区分三种情况:tag存在、isComment、text vnode,下面重点看下最常见的场景tag存在时
1. 首先是通过document.createElement创建该元素
2. setScope 是为了支持 scoped CSS. 特性的。 
- [ ] 记个todo
3. 递归创建孩子 -> createChildren,递归调用,没什么好说的。
```js
function createChildren (vnode, children, insertedVnodeQueue) {
if (Array.isArray(children)) {
for (let i = 0; i < children.length; ++i) {
createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i)
}
} else if (isPrimitive(vnode.text)) {
nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))
}
}
```
4. 触发create相关的钩子,并保存新创建到元素到insertVnodeQueue,后面`invokeInsertHooks`时会用到。(cbs.create是数组是因为会存在多个模块需要处理该元素(主体是模块),而vnode.data.hook.create只是用来处理自身的(主体是自己))
```js
function invokeCreateHooks (vnode, insertedVnodeQueue) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, vnode)
}
i = vnode.data.hook // Reuse variable
if (isDef(i)) {
if (isDef(i.create)) i.create(emptyNode, vnode)
if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
}
}
```
6. 挂载或者衔接(不一定是挂载到界面上)
---
- createElm逻辑如下:
![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1c1cbfeeb3c7479183c4fdaca4064f9c~tplv-k3u1fbpfcp-watermark.image?)
## createComponent
**组件在更新的时候也要重新创建?** 不浪费性能吗?
```js
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) &amp;&amp; i.keepAlive
if (isDef(i = i.hook) &amp;&amp; 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() 整体流程](https://juejin.cn/post/7195084606817861690)对应上了是不是,整个过程两个大的步骤:实例初始化 + 渲染) 
```js
// inline hooks to be invoked on component VNodes during patch
const componentVNodeHooks = {
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (vnode.componentInstance &amp;&amp; !vnode.componentInstance._isDestroyed &amp;&amp; vnode.data.keepAlive) {
// keepAlive 场景,暂忽略
} else {
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
},
//... insert、prepatch、destroy
}
```
2. 上面创建完组件实例后,会将组件vue实例保存到vnode.componentInstance上,如果存在组件实例则
- initComponent:
1. 上面部分介绍`patch`的第二个场景vnode &amp;&amp; !oldVnode会创建一个游离组件,组件等待挂载 ,还没有想到这种场景,记个TODO ❎
2. isPatchable递归向下查找根节点不是组件标签的vnode,返回该vnode.tag,(提供一个例子,记个TODO ❎,为什么要找这个vnode);常规场景返回true即 -> invokeCreateHooks -> setScope(invokeCreateHooks里面执行vnode.data.hook.create,当前这个vnode不会执行,因为不是组件标签vnode❎,待后面补充案例验证)
- ❌ **【待补充】** vnode、vnode.componentInstance、componentInstance._vnode之间的指向关系(我已经有点乱了)
```js
function isPatchable (vnode) {
while (vnode.componentInstance) {
vnode = vnode.componentInstance._vnode
}
return isDef(vnode.tag)
}
function initComponent (vnode, insertedVnodeQueue) {
if (isDef(vnode.data.pendingInsert)) {
//... 创建的游离组件情况(暂时没有挂载)
}
vnode.elm = vnode.componentInstance.$el
if (isPatchable(vnode)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
setScope(vnode)
} else {
// empty component root.
// skip all element-related modules except for ref (#3455)
registerRef(vnode)
// make sure to invoke the insert hook
insertedVnodeQueue.push(vnode)
}
}
```
- insert:document.insertBefore/appendChild
- keepAlive场景下,reactivateComponent,不展开。
---
- createComponent逻辑如下:
![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/98e14429cfcb42ec98a4bb3435f7649e~tplv-k3u1fbpfcp-watermark.image?)
# patchVnode &amp; updateChildren
这两个方法和snabbdom中的实现几乎完全一致,可以[参考](https://juejin.cn/post/7194014863469838393),下面重点说下patchVnode差异部分。
1. 异步组件加载的处理,`return `
2. 静态节点优化处理:isStatic,编译环节会给静态节点添加该标记。[参考官方解释-static-hoisting](https://cn.vuejs.org/guide/extras/rendering-mechanism.html#static-hoisting),目的是对于此类节点在更新时没必要重新构造vnode然后对比。 `return`
3. 关键差异,通过调用vnode.data.hook.prepatch,组件vnode的更新。
```js
// 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](https://github.com/vuejs/vue/commit/d6bef7957541e38cec3051eb4d8c54bda3280eaf)
注意:由于这里给`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的相互引用关系。
一线大厂高级前端编写,前端初中阶面试题,帮助初学者应聘,需要联系微信:javadudu

回复

我来回复
  • 暂无回复内容