最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue源码中的生命周期

    正文概述 掘金(terrence386)   2021-05-09   424

    前情回顾

    理解vdom的前提下,component,element的创建实际上对vnode的实例化。而创建组件的时候会将生命周期混入进去,一起来看下这个生命周期

    生命周期

    生命周期的变量定义在shared文件夹中的constant.js文件中。constant顾名思义,变量嘛。

    // 生命周期钩子数组
    export const LIFECYCLE_HOOKS = [
      'beforeCreate',
      'created',
      'beforeMount',
      'mounted',
      'beforeUpdate',
      'updated',
      'beforeDestroy',
      'destroyed',
      'activated',
      'deactivated',
      'errorCaptured',
      'serverPrefetch'
    ]
    

    同同时这个问价还定义了一个资源类型

    export const ASSET_TYPES = [
      'component',
      'directive',
      'filter'
    ]
    

    定义好了生命周期名称以后,必然需要有个地方去解释执行这些命令。这个位置在core文件夹中的instance文件夹中的lifeCycle.js

    这个文件夹定义了以下几个方法:

    • setActiveInstance设置激活的实例,这里的实例指的是我们写的vue组件或页面。
    • initLifecycle 初始化生命周期。
    • lifecycleMixin 在Vue实例的原型上混入生命周期方法。
    • mountComponent 挂载组件。
    • updateChildComponent 更新子组件。
    • isInInactiveTree 判断当前实例的激活状态。
    • activateChildComponent 激活子组件。
    • deactivateChildComponent 灭活子组件。
    • callHook 调用钩子方法。

    方法细节

    • callHook。callHook的代码如下:
    export function callHook (vm: Component, hook: string) {
      // #7573 disable dep collection when invoking lifecycle hooks
      pushTarget()
      const handlers = vm.$options[hook]
      const info = `${hook} hook`
      if (handlers) {
        for (let i = 0, j = handlers.length; i < j; i++) {
          invokeWithErrorHandling(handlers[i], vm, null, vm, info)
        }
      }
      if (vm._hasHookEvent) {
        vm.$emit('hook:' + hook)
      }
      popTarget()
    }
    

    不得不说,这个源码整的越来越复杂了。其意图是从$options中取出钩子方法,然后遍历执行对应的方法。

    在之前的版本中没有invokeWithErrorHandling这个方法。遍历handlers时直接交给vm调用。

    export function callHook (vm: Component, hook: string) {
      const handlers = vm.$options[hook]
      if (handlers) {
        for (let i = 0, j = handlers.length; i < j; i++) {
          try {
            handlers[i].call(vm)
          } catch (e) {
            handleError(e, vm, `${hook} hook`)
          }
        }
      }
      if (vm._hasHookEvent) {
        vm.$emit('hook:' + hook)
      }
    }
    

    而最新的代码时封装了一个invokeWithErrorHandling方法,执行的时候会包含错误处理。

    这里有个地方我一直不太理解,就是这个_hasHookEvent。从上边的代码里看。如果$optons中的hook即handlers存在,则会调用对应的hook方法。或者如果vm实例的_hasHookEvent属性为true,也会调用$emit方法触发对应的hook方法。

    然后我们看下event.js`。

    const hookRE = /^hook:/
      Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
        const vm: Component = this
        if (Array.isArray(event)) {
          for (let i = 0, l = event.length; i < l; i++) {
            this.$on(event[i], fn)
          }
        } else {
          (vm._events[event] || (vm._events[event] = [])).push(fn)
          // optimize hook:event cost by using a boolean flag marked at registration
          // instead of a hash lookup
          if (hookRE.test(event)) {
            vm._hasHookEvent = true
          }
        }
        return vm
      }
    

    当使用$on方法监听事件时,如果事件名称以hook:为前缀,那么这个事件就会被当做hoodkEvent,在将事件的回调push到实例对象的_event属性时,实例的_hasHookEvent属性会被设置为true。当使用$emit触发$emit('hoook:eventname')时,对应的回调函数就会被触发。

    使用的方式大致如下:

    <LoginComponent @hook:created="created"></LoginComponent>
    
    • initLifecycle。初始化生命周期这个方法,其实只是在实例上申明了几个私有属性,用作申明周期的标识。其代码如下:
    export function initLifecycle (vm: Component) {
      const options = vm.$options
    
      // locate first non-abstract parent
      let parent = options.parent
      if (parent && !options.abstract) {
        while (parent.$options.abstract && parent.$parent) {
          parent = parent.$parent
        }
        parent.$children.push(vm)
      }
    
      vm.$parent = parent
      vm.$root = parent ? parent.$root : vm
     // 设置初始化状态
      vm.$children = []
      vm.$refs = {}
    
      vm._watcher = null
      vm._inactive = null
      vm._directInactive = false
      vm._isMounted = false
      vm._isDestroyed = false
      vm._isBeingDestroyed = false
    }
    

    可以看到,vm设置了$children,$ref,_watcher,_inactve为空,_directInactive,_isMounted,_isDestroyed,_isBeingDestroyed为false。

    • lifecycleMixin生命周期混入方法。

    这个方法在vue的原型上添加了_update$forceupdate,$destroy三个方法。

    Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
        const vm: Component = this
        if (vm._isMounted) {
          callHook(vm, 'beforeUpdate')
        }
        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 */,
            vm.$options._parentElm,
            vm.$options._refElm
          )
          // no need for the ref nodes after initial patch
          // this prevents keeping a detached DOM tree in memory (#5851)
          vm.$options._parentElm = vm.$options._refElm = null
        } else {
          // updates
          vm.$el = vm.__patch__(prevVnode, vnode)
        }
        activeInstance = prevActiveInstance
        // 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.
      }
    

    _update方法会先判断实例是否已经挂载,如果实例已经挂载,则会先调用beforeUpdate钩子。

    然后当前实例的_vnode及当前实例分别赋值给prevNodepreActiveInstance。如果没有preVode则说明是初次渲染,直接调用__pattch__方法进行处理。

    // 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 */,
            vm.$options._parentElm,
            vm.$options._refElm
          )
          // no need for the ref nodes after initial patch
          // this prevents keeping a detached DOM tree in memory (#5851)
          vm.$options._parentElm = vm.$options._refElm = null
        } 
    

    如果存在preVnode则直接调用__patch__进行更新

     // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    
    • $forceUpdate方法。这个放个直接判断实例上是否存在_watcher属性,如果存在_watcher属性,则直接调用watcher的update方法。
    Vue.prototype.$forceUpdate = function () {
        const vm: Component = this
        if (vm._watcher) {
          vm._watcher.update()
        }
      }
    
    • $destroy方法。这个方法会判断实例的_isBeingDestroyed属性,然后执行beforeDestroy钩子函数。然后依次清除watcher,解绑事件(调用$off),设置_isDestroyed属性为true。设置$vnode.parent为null。
    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 && !parent._isBeingDestroyed && !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
        }
      }
    
    • mountComponent挂载组件。这个方法会先判断$options中是否有render方法。如果render不存在,则创建一个空节点。然后调用beforeMount钩子方法。之后,调用实例的_render方法进行更新。最后在实例上设置新的_watcher,以上的步骤完成后,则调用mounted钩子函数。
    export function mountComponent (
      vm: Component,
      el: ?Element,
      hydrating?: boolean
    ): Component {
      vm.$el = el
      if (!vm.$options.render) {
        vm.$options.render = createEmptyVNode
        if (process.env.NODE_ENV !== 'production') {
          /* istanbul ignore if */
          if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
            vm.$options.el || el) {
            warn(
              'You are using the runtime-only build of Vue where the template ' +
              'compiler is not available. Either pre-compile the templates into ' +
              'render functions, or use the compiler-included build.',
              vm
            )
          } else {
            warn(
              'Failed to mount component: template or render function not defined.',
              vm
            )
          }
        }
      }
      callHook(vm, 'beforeMount')
    
      let updateComponent
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        updateComponent = () => {
          const name = vm._name
          const id = vm._uid
          const startTag = `vue-perf-start:${id}`
          const endTag = `vue-perf-end:${id}`
    
          mark(startTag)
          const vnode = vm._render()
          mark(endTag)
          measure(`vue ${name} render`, startTag, endTag)
    
          mark(startTag)
          vm._update(vnode, hydrating)
          mark(endTag)
          measure(`vue ${name} patch`, startTag, endTag)
        }
      } else {
        updateComponent = () => {
          vm._update(vm._render(), hydrating)
        }
      }
    
      vm._watcher = new Watcher(vm, updateComponent, noop)
      hydrating = false
    
      // manually mounted instance, call mounted on self
      // mounted is called for render-created child components in its inserted hook
      if (vm.$vnode == null) {
        vm._isMounted = true
        callHook(vm, 'mounted')
      }
      return vm
    }
    

    总结

    生命周期其实是一个组件从加载到销毁的过程。如果组件初次加载,则会先创建一个空节点,然后调用beforeMount,之后就是使用实例的_render进行渲染,渲染后则调用mounted,设置实例的属性isMounted为true。组件更新的过程会先调用beforeUpdate钩子,之后使用__patch__进行更新操作。组件销毁时会先触发beforeDestroy钩子,然后设置对应的属性为false,对应的示例属性对象为null,最后调用destroyed钩子。

    大致就是这么一个过程。

    思考

    今天也看了些css相关的内容。明天顺带着提一下吧。

    最后说两句

    1. 动一动您发财的小手,「点个赞吧」
    2. 动一动您发财的小手,「点个在看」
    3. 都看到这里了,不妨 「加个关注」
    4. 不妨 「转发一下」,好东西要记得分享

    Vue源码中的生命周期


    起源地下载网 » Vue源码中的生命周期

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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