zoukankan      html  css  js  c++  java
  • vue 组件的 patch

    new Vue({
      el: '#app',
      router,
      components: { App },
      template: '<App/>'
    })
    

    在Vue.prototype._init  执行的时候 ,如果传入了el属性,则调用 vm.$mount 方法挂载 vm  

    实例的挂载最开始是通过$mount方法
    $mount 方法支持传入 2 个参数,第一个是 el,它表示挂载的元素,可以是字符串,也可以是 DOM 对象,
    如果是字符串在浏览器环境下会调用 query 方法转换成 DOM 对象的。第二个参数是和服务端渲染相关,
    在浏览器环境下我们不需要传第二个参数。

    $mount 方法实际上会去调用 mountComponent 方法,这个方法定义在 src/core/instance/lifecycle.js 文件中
    vm.$el 的赋值是在之前 mountComponent 函数做的

    mountComponent 核心就是先实例化一个渲染Watcher

    在它的回调函数中会调用 updateComponent 方法,在此方法中调用 vm._render 方法先生成虚拟 Node,最终调用 vm._update 更新 DOM。

    updateComponent = () => {
          vm._update(vm._render(), hydrating)
        }
    

    render 函数中的 createElement 方法就是 vm.$createElement 方法,vm.$createElement 方法定义是在执行 initRender 方法的时候,内部都调用了 createElement

    Vue.js 利用 createElement 方法创建 VNode,它定义在 src/core/vdom/create-element.js 中

    export function createElement (
      context: Component,
      tag: any,
      data: any,
      children: any,
      normalizationType: any,
      alwaysNormalize: boolean
    ): VNode | Array<VNode> {
      if (Array.isArray(data) || isPrimitive(data)) {
        normalizationType = children
        children = data
        data = undefined
      }
      if (isTrue(alwaysNormalize)) {
        normalizationType = ALWAYS_NORMALIZE
      }
      return _createElement(context, tag, data, children, normalizationType)
    }
    

      

    createElement 方法实际上是对 _createElement 方法的封装,它允许传入的参数更加灵活,在处理这些参数后,调用真正创建 VNode 的函数 _createElement

    _createElement 方法有 5 个参数,context 表示 VNode 的上下文环境,
    tag 表示标签,它可以是一个字符串,也可以是一个 Component
    data 表示 VNode 的数据,它是一个 VNodeData 类型
    children 表示当前 VNode 的子节点,它是任意类型的

    这里先对 tag 做判断,如果是 string 类型,则接着判断如果是内置的一些节点,则直接创建一个普通 VNode,
    如果是为已注册的组件名,则通过 createComponent 创建一个组件类型的 VNode,否则创建一个未知的标签的 VNode。
    如果是 tag 一个 Component 类型,则直接调用 createComponent 创建一个组件类型的 VNode 节点。

    update
    Vue 的 _update 是实例的一个私有方法,它被调用的时机有 2 个,一个是首次渲染,一个是数据更新的时候;
    _update 方法的作用是把 VNode 渲染成真实的 DOM,它的定义在 src/core/instance/lifecycle.js

    _update 的核心就是调用 vm.__patch__ 方法, 路径vuesrccoreinstancelifrcycle.js

    Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
        const vm: Component = this
        const prevEl = vm.$el
        const prevVnode = vm._vnode
        const prevActiveInstance = activeInstance
        activeInstance = 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)
        }
        activeInstance = prevActiveInstance
    
    、、、、、、、、、、、、、、、、、、
    
      }
    、、、、、、、、、、、、、、、、、、、、、、
        
    }
    

      

    if (!prevVnode) {
    // ( 如果是正常的vnode(不是组件vnode)createElm会创建vnode.ele等于一个dom元素 ,然后patch 方法中执行了createElm后,并返回了vnode.ele)
    // 如果这一步是 new 渲染vnode 后 调用 child.$mount(hydrating ? vnode.elm : undefined, hydrating)  执行到这里
      那么 此时的 vm就是渲染vnode 的实例,vm._patch_()会返回 渲染vnode(如果该vnode不是组件vnode) 经过createElm方法创建的dom元素,并赋值给vm.$el

    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */) } else { // updates vm.$el = vm.__patch__(prevVnode, vnode) }

    回到 patch 方法本身,它接收 4个参数,oldVnode 表示旧的 VNode 节点,
    它也可以不存在或者是一个 DOM 对象;vnode 表示执行 _render 后返回的 VNode 的节点;hydrating 表示是否是服务端渲染;removeOnly 是给 transition-group 用的,之后会介绍
    在patch方法中会调用createElm()
    createElm 的作用是通过虚拟节点创建真实的 DOM 并插入到它的父节点中
    里面的createComponent 方法目的是尝试创建子组件
    接下来调用 createChildren 方法去创建子元素,里面会递归调用createElm 方法

    在 createElm 过程中,如果 vnode 节点不包含 tag,则它有可能是一个注释或者纯文本节点,可以直接插入到父元素中

    vm.$vnode 占位符vnode
    vm._vnode 渲染vnode

    第一次new VUE 的时候,会传入el属性,这个属性会成为所有组件挂载的根元素。

    在执行patch方法挂载dom的时候,会执行createElm(vnode,insertedVnodeQueue,parentElm),

    在createElm 方法中组件vnode 和普通vnode 进行了不同的操作

    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
          return
        }
    

    如果是组件vnode执行上面createComponent方法,然后就return了,不再执行createElm下面的代码

    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 */)
          }
        
    // 组件的渲染vnode 的真正插入时机是在下面进行的(注意,此时上面的inir方法递归执行完毕)
    if (isDef(vnode.componentInstance)) { initComponent(vnode, insertedVnodeQueue) insert(parentElm, vnode.elm, refElm) if (isTrue(isReactivated)) { reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm) } return true } } }
    function initComponent (vnode, insertedVnodeQueue) {
        if (isDef(vnode.data.pendingInsert)) {
          insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)
          vnode.data.pendingInsert = null
        }
        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)
        }
      }
    

      

    createComponent 方法里面主要执行了 占位符vode 中的init 方法(vnode.data.hook.init)

    init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
        if (
          vnode.componentInstance &&
          !vnode.componentInstance._isDestroyed &&
          vnode.data.keepAlive
        ) {
          // kept-alive components, treat as a patch
          const mountedNode: any = vnode // work around flow
          componentVNodeHooks.prepatch(mountedNode, mountedNode)
        } else {
        // 占位符vnode.componentInstance 指向 渲染vnode实例vm (createComponentInstanceForVnode会返回渲染vnode的实例) const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ) child.$mount(hydrating ? vnode.elm : undefined, hydrating) // 渲染VNODE的挂载是在这里执行的,vnode.elm 为空,所以渲染vnode 的挂载过程不会执行insert } },

      注意:上面代码中的 vnode 还是占位符vnode,   createComponentInstanceForVnode 会返回一个 vm 实例

    function createComponentInstanceForVnode (
      vnode: any, // we know it's MountedComponentVNode but flow doesn't
      parent: any, // activeInstance in lifecycle state
    ): Component {
      const options: InternalComponentOptions = {
        _isComponent: true,
        _parentVnode: vnode,
        parent
      }
      // check inline-template render functions
      const inlineTemplate = vnode.data.inlineTemplate
      if (isDef(inlineTemplate)) {
        options.render = inlineTemplate.render
        options.staticRenderFns = inlineTemplate.staticRenderFns
      }
      return new vnode.componentOptions.Ctor(options)
    }
    return new vnode.componentOptions.Ctor(options) new 占位符vnode,从新走一遍生命周期,返回一个实例, 在其中会生成一个渲染vnode,然后渲染vnode又回去走挂载

    第一次new vue,肯定又ele,也就是parentElm, 如果render 是一个组件就会在createElm中的createComponent 中执行插入,
    就是把组件vnode的渲染vnode生成dom元素(生成过程中不会插入,因为new渲染vnode的时候没有ele)保存在 组件vnode.ele中,
    再执行 insert(parentElm, vnode.elm, refElm)。在createElm方法中,
    下面会执行createChildren(vnode, children, insertedVnodeQueue),里面会递归调用createElm,
    并把上一个的组件的渲染vnode 生成的 DOM 作为parentElm参数传入createElm。依次递归执行,
    生成一个关系树,然后递归执行完毕会按照程序进行先子后父的依次插入。
    最后递归遍历所有生成一个结构DOM插入到body
  • 相关阅读:
    Redis:五、Redis持久化
    Redis:四、jedis连接redis服务器
    Redis:三、Key和Value
    php 拆分的 string里包含“2”或“1”符号(“”或者“”)
    清除float浮动
    js 判断数据类型
    form表单里target属性(在新窗口打开页面)
    think PHP5实现文件下载
    echarts自定义提示框内容
    Chrome浏览器不支持小于12px的字体大小
  • 原文地址:https://www.cnblogs.com/liumingwang/p/14519363.html
Copyright © 2011-2022 走看看