Vue2源码梳理:update的整体实现流程

这篇具有很好参考价值的文章主要介绍了Vue2源码梳理:update的整体实现流程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

update

  • 回到之间 $mount时,mountComponent 函数的过程,vm._render 是如何创建了一个 VNode
  • 接下来就是要把这个 VNode 渲染成一个真实的 DOM 并渲染出来,这个过程是通过 vm._update 完成的
  • _update 它是一个vue 的私有方法,它把我们的 vnode 渲染成真实的 dom
  • _update的方法,也是原型上的一个方法, 它的定义在 src/core/instance/lifecycle.js 中
    export function lifecycleMixin (Vue: Class<Component>) {
      Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
        // 首先它定义一些变量,这些变量实际上都是给之后数据更新时候用的
        // 因为update它的调用实际有两个,一个是我们首次渲染的时候,它会调用update把我们的vnode映射是真实的dom
        // 还有一个就是当我们去改变数据的时候,数据改变也会驱动视图的变化,它同样也会调update方法
        // 可以初步认为它们都是空
        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.
        // 这个 prevVnode 也是空,它会走到这个逻辑,就是 initial render,否则会走到 updates
        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 && vm.$parent && 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.
      }
      // ...
    }
    
    • 这里 vm.__patch__ 定义在 src/platforms/web/runtime/index.js 中
      Vue.prototype.__patch__ = inBrowser ? patch : noop
      
      • 这里浏览器环境,才会有 patch, 这里的 inBrowser
        // src/core/util/env.js
        export const inBrowser = typeof window !== 'undefined'
        
      • vue它是可以跑在服务端的,在服务端的 patch 是一个空函数,因为在服务端不会有dom
    • 进入 patch, 定义在 src/platforms/web/runtime/patch.js
      import * as nodeOps from 'web/runtime/node-ops'
      import { createPatchFunction } from 'core/vdom/patch'
      import baseModules from 'core/vdom/modules/index'
      import platformModules from 'web/runtime/modules/index'
      
      // the directive module should be applied last, after all
      // built-in modules have been applied.
      const modules = platformModules.concat(baseModules)
      
      // 这个 patch 方法,调用了这个 createPatchFunction, 返回来是一个函数,特别像高阶函数这样的概念
      // nodeOps 是操作dom的集合方法;而 modules 是 platformModules 和 baseModules,这里定义属性,类和钩子函数等
      
      // 这里才是真正的 patch 函数,vue饶了一个大圈子,为什么会定义在这里
      // 他利用函数柯里化的技巧,一开始传入 nodeOps, modules 都是和平台相关的
      // vue 是跨平台的,可以在node上跑,可以在浏览器上跑,可以在 weex 上
      // 在web上真实操作dom的api 和 weex 上是不同的,虽然最终可以生成同样的 vnode
      // 这块是分离的, 所以 dom操作的api通过参数来传入
      
      // 另外就是 models 模块,它的一些生命周期,在不同的生命周期要做的事情,然后怎么去生成这些模块和我们dom做配合, 这些东西,跟平台也是相关的
      // 这里有 baseModules 和 platformModules
      // 通过函数柯里化,可基于不同参数来干不同的事情,把写代码的差异去搞定
      // 等到真实执行 patch 的时候,就不用考虑平台的区别了
      // 因为我们是把平台差异化的东西都已经在之前抹平了
      
      // 所以这样的话,就可以每次调用这个 patch 的时候,完全不用关心真实操作是什么
      // 因为在之前已经把这个参数提前传入了,相当于通过这种闭包的技巧,实现了对 nodeOps 和 modules 的持有
      // 接下来真实调每次的方法的时候,都不用再去传这些差异化的参数,这就是一个非常好的一个js的编程技巧
      export const patch: Function = createPatchFunction({ nodeOps, modules })
      
      • 进入 createPatchFunction, 定义在 src/core/vdom/patch.js
        // 在patch阶段中,会执行各个钩子
        const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
        
        export function createPatchFunction (backend) {
          let i, j
          const cbs = {}
        
          // 拿到 这两项
          const { modules, nodeOps } = backend
        
          // 将 hooks 遍历到 cbs 对象中保存
          for (i = 0; i < hooks.length; ++i) {
            cbs[hooks[i]] = []
            // 遍历所有模块,拿到所有模块的钩子
            for (j = 0; j < modules.length; ++j) {
              if (isDef(modules[j][hooks[i]])) {
                cbs[hooks[i]].push(modules[j][hooks[i]])
              }
            }
          }
        
          // ... 这里定义了非常多的辅助函数
        
          // 这个patch实际上是传入了四个参数
          // 关注前两个,oldVnode 是 真实的el, vnode 就是vdom, 后面两个都是 false
          return function patch (oldVnode, vnode, hydrating, removeOnly) {
            // 销毁时 return 跳过
            if (isUndef(vnode)) {
              if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
              return
            }
        
            let isInitialPatch = false
            const insertedVnodeQueue = []
        
            // 这个 if 一般不会走到,跳过
            if (isUndef(oldVnode)) {
              // empty mount (likely as component), create new root element
              isInitialPatch = true
              createElm(vnode, insertedVnodeQueue)
            } else {
              // 这里,判断是否是真实的dom,这里是 true
              const isRealElement = isDef(oldVnode.nodeType)
              if (!isRealElement && sameVnode(oldVnode, vnode)) {
                // patch existing root node
                patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
              } else {
                // 这里执行
                if (isRealElement) {
                  // mounting to a real element
                  // check if this is server-rendered content and if we can perform
                  // a successful hydration.
                  // 这里不是 ssr, 这里走不到
                  if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
                    oldVnode.removeAttribute(SSR_ATTR)
                    hydrating = true
                  }
                  // 这里浏览器dom环境下,也走不到
                  if (isTrue(hydrating)) {
                    if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
                      invokeInsertHook(vnode, insertedVnodeQueue, true)
                      return oldVnode
                    } else if (process.env.NODE_ENV !== 'production') {
                      warn(
                        'The client-side rendered virtual DOM tree is not matching ' +
                        'server-rendered content. This is likely caused by incorrect ' +
                        'HTML markup, for example nesting block-level elements inside ' +
                        '<p>, or missing <tbody>. Bailing hydration and performing ' +
                        'full client-side render.'
                      )
                    }
                  }
                  // either not server-rendered, or hydration failed.
                  // create an empty node and replace it
                  oldVnode = emptyNodeAt(oldVnode) // 这个方法就是把真实的dom转化为 vnode
                }
        
                // replacing existing element
                const oldElm = oldVnode.elm
                const parentElm = nodeOps.parentNode(oldElm) // 获取父节点
        
                // 这个函数很重要,把vnode挂载到真实dom上
                // create new node
                createElm(
                  vnode,
                  insertedVnodeQueue,
                  // extremely rare edge case: do not insert if old element is in a
                  // leaving transition. Only happens when combining transition +
                  // keep-alive + HOCs. (#4590)
                  oldElm._leaveCb ? null : parentElm,
                  nodeOps.nextSibling(oldElm)
                )
        
                // update parent placeholder node element, recursively
                if (isDef(vnode.parent)) {
                  let ancestor = vnode.parent
                  const patchable = isPatchable(vnode)
                  while (ancestor) {
                    for (let i = 0; i < cbs.destroy.length; ++i) {
                      cbs.destroy[i](ancestor)
                    }
                    ancestor.elm = vnode.elm
                    if (patchable) {
                      for (let i = 0; i < cbs.create.length; ++i) {
                        cbs.create[i](emptyNode, ancestor)
                      }
                      // #6513
                      // invoke insert hooks that may have been merged by create hooks.
                      // e.g. for directives that uses the "inserted" hook.
                      const insert = ancestor.data.hook.insert
                      if (insert.merged) {
                        // start at index 1 to avoid re-invoking component mounted hook
                        for (let i = 1; i < insert.fns.length; i++) {
                          insert.fns[i]()
                        }
                      }
                    } else {
                      registerRef(ancestor)
                    }
                    ancestor = ancestor.parent
                  }
                }
        
                // destroy old node
                if (isDef(parentElm)) {
                  removeVnodes(parentElm, [oldVnode], 0, 0) // 删除旧的节点
                } else if (isDef(oldVnode.tag)) {
                  invokeDestroyHook(oldVnode)
                }
              }
            }
        
            invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
            return vnode.elm
          }
        }
        
        • 进入 createElm
          function createElm (
            vnode,
            insertedVnodeQueue,
            parentElm,
            refElm,
            nested,
            ownerArray,
            index
          ) {
            // 这个一版不会走到,跳过
            if (isDef(vnode.elm) && 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)) {
              if (process.env.NODE_ENV !== 'production') {
                if (data && data.pre) {
                  creatingElmInVPre++
                }
                // 这种错误是没有注册组件,却使用组件
                if (isUnknownElement(vnode, creatingElmInVPre)) {
                  warn(
                    'Unknown custom element: <' + tag + '> - did you ' +
                    'register the component correctly? For recursive components, ' +
                    'make sure to provide the "name" option.',
                    vnode.context
                  )
                }
              }
              // 这里生成原生dom节点
              vnode.elm = vnode.ns
                ? nodeOps.createElementNS(vnode.ns, tag)
                : nodeOps.createElement(tag, vnode)
              setScope(vnode)
              
              // 跳过
              /* istanbul ignore if */
              if (__WEEX__) {
                // ...
              } else {
                // 创建子节点
                createChildren(vnode, children, insertedVnodeQueue)
                if (isDef(data)) {
                  invokeCreateHooks(vnode, insertedVnodeQueue)
                }
                insert(parentElm, vnode.elm, refElm)
              }
          
              if (process.env.NODE_ENV !== 'production' && data && data.pre) {
                creatingElmInVPre--
              }
            } 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)
            }
          }
          
          • createElm 的作用是通过虚拟节点创建真实的 DOM 并插入到它的父节点中
          • 这里的 createComponent 方法目的是尝试创建子组件,在当前这个 case 下它的返回值为 false
          • 接下来判断 vnode 是否包含 tag,如果包含,先简单对 tag 的合法性在非生产环境下做校验,看是否是一个合法标签
          • 然后再去调用平台 DOM 的操作去创建一个占位符元素
            vnode.elm = vnode.ns
              ? nodeOps.createElementNS(vnode.ns, tag)
              : nodeOps.createElement(tag, vnode)
            
          • 进入 createChildren
            // createChildren(vnode, children, insertedVnodeQueue)
            function createChildren (vnode, children, insertedVnodeQueue) {
              if (Array.isArray(children)) {
                // 在 dev 环境对节点 key 做校验
                if (process.env.NODE_ENV !== 'production') {
                  checkDuplicateKeys(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)))
              }
            }
            
            • 如果是array, 遍历children 递归调用createElm
            • 其实 createChildren 很简单,如果 这个 vnode 还有子节点
            • 就递归调用子节点的 createElm, 一层层去处理
          • 接着再调用 invokeCreateHooks 方法执行所有的 create 的钩子并把 vnode push 到 insertedVnodeQueue 中
            if (isDef(data)) {
              invokeCreateHooks(vnode, insertedVnodeQueue)
            }
            
            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)
              }
            }
            
          • 最终会调用 insert 操作, 定义在 src/core/vdom/patch.js
          • insert 方法把 DOM 插入到父节点中,因为是递归调用
          • 子元素会优先调用 insert,所以整个 vnode 树节点的插入顺序是先子后父
            insert(parentElm, vnode.elm, refElm)
            
            function insert (parent, elm, ref) {
              if (isDef(parent)) {
                if (isDef(ref)) {
                  if (ref.parentNode === parent) {
                    nodeOps.insertBefore(parent, elm, ref)
                  }
                } else {
                  nodeOps.appendChild(parent, elm)
                }
              }
            }
            
            // src/platforms/web/runtime/node-ops.js
            export function insertBefore (parentNode: Node, newNode: Node, referenceNode: Node) {
              parentNode.insertBefore(newNode, referenceNode)
            }
            export function appendChild (node: Node, child: Node) {
              node.appendChild(child)
            }
            
            • 其实就是调用原生 DOM 的 API 进行 DOM 操作
  • 以上,我们可知,update 操作就是 patch 操作,里面的细节其实是非常多的
  • 这里只是梳理整体的调用流程
  • 从 new Vue 到渲染dom到页面的大致过程如下
  • new Vue -> init -> $mount -> compile(可选,存在编译版本) -> render -> vnode -> patch -> dom

文章来源地址https://www.toymoban.com/news/detail-829104.html

到了这里,关于Vue2源码梳理:update的整体实现流程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Vue:将以往的JQ页面,重构成Vue组件页面的大致思路梳理(组件化编码大致流程)

    组件要按照功能点拆分,命名不要与 HTML元 素冲突。 1、根据 UI 提供的原型图,进行结构设计,结构设计的粒度以是否方便给组件起名字为依据。并梳理好对应组件的层级依赖关系。 2、设计好结构后,开始写对应的组件,并根据梳理好的层级关系,对应引入组件。 3、根据

    2024年01月15日
    浏览(55)
  • 【springboot+vue项目(十四)】基于Oauth2的SSO单点登录(一)整体流程介绍

    场景:现在有一个前后端分离的系统,前端框架使用vue-element-template,后端框架使用springboot+springSecurity+JWT+Redis(登录部分)现在需要接入到 已经存在的第三方基于oauth2.0的非标准接口 统一认证系统。  温馨提示:如果是接入到 基于oauth2.0的 标准接口的认证服务系统,可以直

    2024年02月19日
    浏览(44)
  • Vue2 API-源码

    目录 Vue.extend(option) delimiters functional  Vue.component(id, Function | Object)  Vue.directive( id, [definition] ) Vue.filter( id, function) Vue.nextTick() Vue.set() Vue.delete(target, index/key) Vue.compile(template) Vue.observable(object) provide/inject extends、mixins 作用:返回一个vue子组件的构造函数 参数:创建vue实例的参数

    2023年04月11日
    浏览(48)
  • vue2源码(九)-- 挂载阶段

    1、 $mounted 函数实际上调用的是 mountComponent 函数,该函数首先判断是否存在 render 函数,如果不存在,则给一个默认的渲染函数,该渲染函数会创建一个注释类型的 VNode 节点,然后给出警告。 2、触发 beforeMount 声明周期函数 3、定义 updateComponent 函数,该函数内部首先执行 r

    2023年04月08日
    浏览(29)
  • 【Vue2.0源码学习】指令篇-Vue自定义指令

    在 Vue 中,除了 Vue 本身为我们提供的一些内置指令之外, Vue 还支持用户自定义指令。并且用户有两种定义指令的方式:一种是使用全局API—— Vue.directive 来定义全局指令,这种方式定义的指令会被存放在 Vue.options[\\\'directives\\\'] 中;另一种是在组件内的 directive 选项中定义专为

    2024年02月09日
    浏览(45)
  • vue2中实现Antv g6 流程图 以及自定义节点

    本案例 antv g6版本: \\\"@antv/g6\\\": \\\"^3.4.8\\\", 效果: 1.引入antv g6和echarts差不多 一定记得给盒子设置宽高 初学者一个,以上如有问题或者错误,多谢指出

    2024年02月13日
    浏览(59)
  • vue2的生命周期详解(代码演示+源码)

            生命周期是指从开始创建、初始化数据、编译模版、挂载 Dom - 渲染、更新 - 渲染、卸载等一系列过程,我们称这是 Vue 的生命周期,它主要强调一个时间段。用一句话来概括就是: Vue实例的生命周期: 从创建到销毁的整个过程 Vue框架内置函数,随着组件的生命周期阶

    2024年02月04日
    浏览(49)
  • 【Vue2.0源码学习】全局API篇-Vue中全局API分析

    与实例方法不同,实例方法是将方法挂载到 Vue 的原型上,而全局API是直接在 Vue 上挂载方法。在 Vue 中,全局API一共有12个,分别是 Vue.extend 、 Vue.nextTick 、 Vue.set 、 Vue.delete 、 Vue.directive 、 Vue.filter 、 Vue.component 、 Vue.use 、 Vue.mixin 、 Vue.observable 、 Vue.version 。这12个API中有的

    2024年02月08日
    浏览(47)
  • 【vue2+antvx6】节点大小不一致,点击按钮流程图自动布局

    需求: 1、点击优化布局的按钮,自动布局(从左到右),按钮变成撤销布局按钮 2、点击撤销布局的按钮,返回之前的布局 3、在点击优化布局的按钮后,如果移动了节点,则自动将撤销布局的按钮变成优化布局的按钮 第一步:安装插件 第二步:写方法  全部代码

    2024年04月28日
    浏览(34)
  • Untiy在update生命周期里UI的文字提示连续两次,进行慢慢进入慢慢消失操作,整体为流程控制逻辑加上DOTween的使用

    很棒一个算法逻辑,记录一下笔记,与大家分享。 主要是对Unity开发时一个文字提示同时在页面慢慢出现慢慢消失两次,显示不同的文字提示。 具体功能类似于CF的挑战模式,在打完这一关进入下一关的这个时间段会出现一个文字提示表示这一关结束,然后消失,又出现一个

    2024年02月01日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包