Vue之组件更新


示例1:新旧节点不同

<template>
  <div id="app">
    <hello-world :flag="flag" />
    <button @click="toggle">toggle</button>
  </div>
</template>

<script> import HelloWorld from './components/HelloWorld/index' export default { name: 'App', components: { HelloWorld }, data() { return { flag: true }; }, methods: { toggle() { this.flag = !this.flag; } } } </script>
<template>
  <div class="hello" v-if="flag">
    <h1>{{ nested.msg }}</h1>
    <h2>Essential links</h2>
    <h2>ecosystem</h2>
    <ul>
      <li><a href="" target="_blank">vur-router</a></li>
      <li><a href="" target="_blank">vur-router</a></li>
      <li><a href="" target="_blank">vur-router</a></li>
      <li><a href="" target="_blank">vur-router</a></li>
      <li><a href="" target="_blank">vur-router</a></li>
    </ul>
  </div>
  <ul v-else>
    <li><a href="" target="_blank">Twitter</a></li>
    <li><a href="" target="_blank">Twitter</a></li>
    <li><a href="" target="_blank">Twitter</a></li>
    <li><a href="" target="_blank">Twitter</a></li>
    <li><a href="" target="_blank">Twitter</a></li>
  </ul>
</template>

<script> export default { name: "index", props: ['flag'], data() { return { nested: { mag: 'welcome to your vue.js app' } }; } } </script>
  • 当点击toggle时,首先触发setter,然后走到App组件的渲染watcher update,在nextTick中执行flushSchedulerQueue(标记 flushing 为 true ),执行watcher.run,执行get。

  • 走到App组件的patch,会走到sameVnode

function sameVnode (a, b) { return ( a.key === b.key && ( ( a.tag === b.tag && a.isComment === b.isComment && isDef(a.data) === isDef(b.data) && sameInputType(a, b) ) || ( isTrue(a.isAsyncPlaceholder) && a.asyncFactory === b.asyncFactory && isUndef(b.asyncFactory.error) ) ) ) }
  • 此时判断新旧节点相同,进入patchVnode,当更新的vnode是一个组件vnode的时候,会执行prepatch方法,此时会执子组件(HelloWorld组件)的prepatch钩子函数

if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { i(oldVnode, vnode) }
  • 该函数执行updateChildComponent,该函数对子组件数据做更新(对$attrs,$listeners,props等),更新flag的变化时,触发dep.notify(),执行子组件的update,此时由于flushing会true,渲染watcher会进入当前queue,所以父子组件的更新在一个tick中。

  • 进入到子组件的ptach,此时新旧节点不同,会进入新旧节点不同的逻辑,首先创建vode,然后执行更新父组件的占位符vnode,首先判断当前vnode是否可挂载,最后移除旧节点,插入新节点。

 

 

 

示例2:新旧节点相同且都有子节点

<template>
  <div id="app">
    <div>
      <ul>
        <li v-for="item in items" :key="item.id">{{ item.val }}</li>
      </ul>
    </div>
    <button @click="change">change</button>
  </div>
</template>

<script>
// import HelloWorld from './components/HelloWorld/index'
export default { name: 'App', components: { // HelloWorld
 }, data() { return { items: [ { id: 0, val: 'A'}, { id: 1, val: 'B'}, { id: 2, val: 'C'}, { id: 3, val: 'D'}, ] }; }, methods: { change() { this.items.reverse().push({ id: 4, val: 'E'}); } } } </script>

 

  • 点击change按钮进入App组件的patch过程,此时新旧节点不同且都存在子节点进入patchVnode,分别拿新旧节点的child,进入updateChildren,此函数是组件更新核心也是最复杂的
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { if (isUndef(oldStartVnode)) { oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
      } else if (isUndef(oldEndVnode)) { oldEndVnode = oldCh[--oldEndIdx] } else if (sameVnode(oldStartVnode, newStartVnode)) { patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue) oldStartVnode = oldCh[++oldStartIdx] newStartVnode = newCh[++newStartIdx] } else if (sameVnode(oldEndVnode, newEndVnode)) { patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue) oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx] } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
 patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue) canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)) oldStartVnode = oldCh[++oldStartIdx] newEndVnode = newCh[--newEndIdx] } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
 patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue) canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) oldEndVnode = oldCh[--oldEndIdx] newStartVnode = newCh[++newStartIdx] } else { if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) if (isUndef(idxInOld)) { // New element
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) } else { vnodeToMove = oldCh[idxInOld] if (sameVnode(vnodeToMove, newStartVnode)) { patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue) oldCh[idxInOld] = undefined canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm) } else { // same key but different element. treat as new element
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) } } newStartVnode = newCh[++newStartIdx] } }
  • 该函数的核心是执行新旧vnode的diff算法,根据不同情况做不同的更新逻辑,递归调用patchVnode方法,最后执行完之后页面已经被替换成了新节点。

 

 

总结:

1、组件更新的过程核心就是新旧vnode diff算法,对新旧节点相同以及不同的情况分别做不同的处理。

2、新旧节点不同的更新流程是:创建新节点=>更新父组件占位符=>删除旧节点。

3、新旧节点相同的更新流程是去获取它们的child,根据不同情况做不同的更新逻辑。

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM