最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 【vue】什么是vnode hook?

    正文概述 掘金(丢了帽子的路飞)   2021-04-06   458

    1、什么是vnode hook?

    在对新旧vnode进行patch的过程中,在不同的阶段提供的一些访问vnode的钩子。vnode hook贯穿整个patch的过程, 像dom元素的属性、事件、class、style的更新,ref, 指令,transition都是通过vnode hook来实现的。patch方法是处理diff的整体流程逻辑的,而hook就是用来干活的。

    2、组件vnode的hook。

    1. init:创建内部组件时调用

    【vue】什么是vnode hook? 在init内部会根据vnode创建组件实例:

      init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
        if (
          vnode.componentInstance &&
          !vnode.componentInstance._isDestroyed &&
          vnode.data.keepAlive
        ) {
          // kept-alive components, treat as a patch
          // 如果是使用了keepAlive的组件,则会复用上一次的组件实例
          const mountedNode: any = vnode // work around flow
          // 并且进行prepatch
          componentVNodeHooks.prepatch(mountedNode, mountedNode)
        } else {
          // 创建组件实例
          // 在组件的vnode init的时候会创建对应的组件实例
          // init执行完也代表组件已经创建成功,dom已经在内存中创建出来了
          // 真正执行mounted钩子是在根节点的dom元素生成后, 通过insertedVnodeQueue来批量执行的
          const child = vnode.componentInstance = createComponentInstanceForVnode(
            vnode, // VNode节点对象
            activeInstance // 父组件实例
          )
          // 挂载组件
          child.$mount(hydrating ? vnode.elm : undefined, hydrating)
        }
      },
    
    1. insert: 当整个vnode树都patch完成才会执行,通过维持insertedVnodeQueue来保证所有组件的mouted顺序执行。
      function invokeInsertHook (vnode, queue, initial) {
        // delay insert hooks for component root nodes, invoke them after the
        // element is really inserted
        // 只有根组件的mounted事件是在自己patch完成后执行的
        // 内部组件不是这样的,虽然内部组件patch完成后dom元素已经生成了并且插入到了父节点中
        // 但是mounted生命周期是延迟执行的。
        if (isTrue(initial) && isDef(vnode.parent)) {
          // 将当前vnode的插入队列放到父组件的pendingInsert上
          vnode.parent.data.pendingInsert = queue
        } else {
          // 遍历vnode队列,执行vonde.data.hook.insert
          for (let i = 0; i < queue.length; ++i) {
            queue[i].data.hook.insert(queue[i])
          }
        }
      }
    

    执行mounted

      insert (vnode: MountedComponentVNode) {
        const { context, componentInstance } = vnode
        // 在子组件调用$mount成功后并不会触发 mounted 钩子, 只有根组件才会调用
        // 子组件的mounted是在这里触发的
        // 这样可以保证在任何一个子组件的mounted里通过refs访问任何组件的实例都能有结果
        // 假设是在$mount里调用mounted,此时子组件的下一个兄弟组件还未创建成功
        // 这样的话是访问不到下一个兄弟组件的
        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 */)
          }
        }
      },
    
    1. prepatch:对组件patch之前调用
    function patchVnode() {
        // ....
        if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
          i(oldVnode, vnode)
        }
        // ....
    }
    

    prepatch的逻辑:

      prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
        // 当vnode进行patch时会对上一次创建出来的组件实例进行复用
        // 然后对组件的属性、事件、子节点进行更新
        const options = vnode.componentOptions
        const child = vnode.componentInstance = oldVnode.componentInstance
        debugger
        updateChildComponent(
          child,
          options.propsData, // updated props
          options.listeners, // updated listeners
          vnode, // new parent vnode
          options.children // new children
        )
      },
    
    1. destroy:vnode被销毁时调用
      function invokeDestroyHook (vnode) {
        let i, j
        const data = vnode.data
        if (isDef(data)) {
          // 先调用用户定义的destroy hook, 再调用vue定义的destroy hook
          if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)
          for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)
        }
        // 如果存在children, 则递归调用
        if (isDef(i = vnode.children)) {
          for (j = 0; j < vnode.children.length; ++j) {
            invokeDestroyHook(vnode.children[j])
          }
        }
      }
    

    destroy的逻辑:

    destroy (vnode: MountedComponentVNode) {
        // 当vnode节点销毁时对应的组件实例也会被销毁
        const { componentInstance } = vnode
        if (!componentInstance._isDestroyed) {
          // 如果不是keep-alive组件, 直接销毁组件
          if (!vnode.data.keepAlive) {
            componentInstance.$destroy()
          } else {
            // keep-alive组件
            deactivateChildComponent(componentInstance, true /* direct */)
          }
        }
    }
    

    3、普通的vnode的hook

    1. create:vnode对应的dom元素创建完成时调用。
      • updateAttrs:更新dom元素的普通属性
      • updateClass:更新dom元素的class
      • updateDOMListeners:更新dom元素的事件
      • updateDOMProps:更新dom元素的innerHTML、textContent等特殊属性。
      • updateStyle:更新dom元素的style
      • _enter:transition组件使用,动画逻辑是在这里定义的
      • create:registerRef注册ref
      • updateDirectives:更新指令
    2. update
      • updateAttrs(oldVnode, vnode)
      • updateClass(oldVnode, vnode)
      • updateDOMListeners(oldVnode, vnode)
      • updateDOMProps(oldVnode, vnode)
      • updateStyle(oldVnode, vnode)
      • update(oldVnode, vnode)
      • updateDirectives(oldVnode, vnode)
    3. remove: dom元素从页面中移除前调用,transition会使用到。
    4. destroy
      • destroy(vnode): 销毁ref
      • unbindDirectives(vnode):指令unbind
    5. active: keep-alive组件重新展示时调用,transition会使用到。

    4、vnode hook 测试代码

    有兴趣的朋友可以跑一下下面的代码,可能会对patch的过程理解有帮助。我这篇文章可能是从不同的角度去看patch,已经有点钻牛角尖了, 如果对vue的pacth过程感兴趣的朋友, 刚开始千万不要迷失在这些hook里面。知道大概是做什么的就好, 还是得从整体流程去理解。比如指令、ref、dom的等建议不要和patch的整体逻辑一起研究,单独拿出来研究即可。

          const Comp = {
            data() {
              return {
                msg: 1,
              };
            },
            props: {
              msg1: String,
            },
            methods: {
              handleClick() {
                this.msg = Math.random();
              },
            },
            render(h) {
              let vm = this;
              return h(
                "div",
                {
                  hook: {
                    // 创建的时候调用的hook
                    create(oldVnode, vnode) {
                      //  这个时候已经根据vnode创建出dom节点了, 但是还未插入到父节点中去
                      console.log("create", oldVnode, vnode);
                      vnode.elm.style.cssText = "color: red";
                    },
                    insert(oldVnode, vnode) {
                      console.log("insert", oldVnode, vnode);
                    },
                    // 更新时调用的 hook
                    prepatch(oldVnode, vnode) {
                      console.log("prepatch", oldVnode, vnode);
                    },
                    update(oldVnode, vnode) {
                      console.log("update", oldVnode, vnode);
                    },
                    postpatch(oldVnode, vnode) {
                      console.log("postpatch", oldVnode, vnode);
                    },
                    destroy(oldVnode, vnode) {
                      console.log("destroy", oldVnode, vnode);
                    },
                  },
                  on: {
                    click: this.handleClick,
                  },
                },
                [
                  h(
                    "button",
                    {
                      on: {
                        click: this.handleClick,
                      },
                    },
                    "组件内部修改状态"
                  ),
                  this.msg + "  " + this.msg1,
                ]
              );
            },
          };
          new Vue({
            el: "#app",
            components: { Comp },
            data() {
              return {
                showComp: true,
                msg1: "a",
              };
            },
            methods: {
              handleToggle() {
                this.showComp = !this.showComp;
              },
              changeMsg1() {
                this.msg1 = Math.random() + "";
              },
            },
            render(h) {
              return h("div", [
                h(
                  "button",
                  {
                    on: {
                      click: this.handleToggle,
                    },
                  },
                  this.showComp ? "隐藏" : "显示"
                ),
                h(
                  "button",
                  {
                    on: {
                      click: this.changeMsg1,
                    },
                  },
                  "给子组件发送消息"
                ),
                this.showComp
                  ? h("Comp", {
                      props: {
                        msg1: this.msg1,
                      },
                      hook: {
                        init(vnode, hydrating) {
                          console.log("组件Comp init", vnode, hydrating);
                        },
                        insert(vnode) {
                          console.log("组件Comp insert", vnode);
                        },
                        prepatch(oldVnode, vnode) {
                          console.log("组件Comp prepatch", oldVnode, vnode);
                        },
                        postpatch(oldVnode, vnode) {
                          console.log("组件Comp postpatch", oldVnode, vnode);
                        },
                        destroy(vnode) {
                          console.log("组件Comp destroy", vnode);
                        },
                      },
                    })
                  : null,
              ]);
            },
          });
    

    起源地下载网 » 【vue】什么是vnode hook?

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元