1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Vue 源碼解析 (三)初始化生命周期流程

        共 24919字,需瀏覽 50分鐘

         ·

        2021-05-08 19:52

        Vue 源碼解析 (三)初始化生命周期流程

        先來看以下這個簡單的生命周期例子:

        const vm = new Vue({
                        el"#app",
                        components: {
                            "comp-a": compA
                        },
                        beforeCreate() {
                            console.log("beforeCreate")
                        },
                        created() {
                            console.log("created")
                        },
                        mounted() {
                            console.log("mounted")
                        },
                        beforeUpdate() {
                            console.log("beforeUpdate")
                        },
                        updated() {
                            console.log("updated")
                        },
                        beforeDestroy() {
                            console.log("beforeDestroy")
                        },
                        destroyed() {
                            console.log("destroyed")
                        }
                    })

        可以看到先后執(zhí)行了 beforeCreate, created, mounted, 為什么沒有執(zhí)行 updated, 是因為我們沒有手動觸發(fā)更新,我們可以嘗試著觸發(fā)手動更新下;

        mounted() {
           this.$forceUpdate();
           console.log("mounted")
        },

        同理我們也需要手動觸發(fā)銷毀動作:

        mounted() {
          this.$destroy();
          console.log("mounted")
        },

        setActiveInstance

        設置激活的組件實例對象,是因為存在 keep-alive 的情況,所以需要處理:

        • 保存上一個激活對象
        • 保存 vm 為當前激活對象
        • 返回函數(shù)
        function setActiveInstance(vm{
            var prevActiveInstance = activeInstance;
            activeInstance = vm;
            return function ({
              activeInstance = prevActiveInstance;
            }
          }

        initLifecycle

        初始化生命周期,當前的 vm 對象出現(xiàn)以下幾個屬性:


            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;

        我們來看下例子:

        const compA = {
            template"<div>我是compA</div>"
        }
        const vm = new Vue({
            el"#app",
            components: {
                "comp-a": compA
            }
        })
        console.log(vm)

        initLifecycle() 函數(shù)的具體代碼如下:

        function initLifecycle(vm{
            /*獲取到options, options已經(jīng)在mergeOptions中最終處理完畢*/
            var options = vm.$options;

            // locate first non-abstract parent
            /*獲取當前實例的parent*/
            var parent = options.parent;
            /*parent存在, 并且不是非抽象組件*/
            if (parent && !options.abstract) {
                /*循環(huán)向上查找, 知道找到是第一個非抽象的組件的父級組件*/
                while (parent.$options.abstract && parent.$parent) {
                    parent = parent.$parent;
                }
                /*將當前的組件加入到父組件的$children里面.  此時parent是非抽象組件 */
                parent.$children.push(vm);
            }
            /*設置當前的組件$parent指向父級組件*/
            vm.$parent = parent;
            vm.$root = parent ? parent.$root : vm;

            /*設置vm的一些屬性*/
            vm.$children = [];
            vm.$refs = {};

            vm._watcher = null;
            vm._inactive = null;
            vm._directInactive = false;
            vm._isMounted = false;
            vm._isDestroyed = false;
            vm._isBeingDestroyed = false;
        }

        從上面的 if 開始, 成立的條件是: 當前組件有 parent 屬性, 并且是非抽象組件. 才進入 if 語句. 然后通過 while 循環(huán).向上繼續(xù)查到 第一個非抽象組件. 然后做了兩件事:

        將當前的 vm 添加到查找到的第一個非抽象父級組件 $children 中

         parent.$children.push(vm);

        將當前的組件的$parent,指向查找到的第一個非抽象組件

        vm.$parent = parent;

        之后的代碼給vm設置了一些屬性

        Vue.prototype._update

        Vue.prototype._update = function (vnode: VNode, hydrating?: boolean{
            const vm: Component = this
            const prevEl = vm.$el // 拿到上一次更新元素
            const prevVnode = vm._vnode // 拿到上一次更新的虛擬節(jié)點
            const restoreActiveInstance = setActiveInstance(vm) // 緩存當前實例
            vm._vnode = vnode
            // Vue.prototype.__patch__ is injected in entry points
            // based on the rendering backend used.
            if (!prevVnode) {
             // 如果上一次沒有更新過,就直接與 vm.$el,vnode 對比更新
              // initial render
              vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
            } else {
              // updates
              // 否則就跟上一個節(jié)點對比跟新
              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.
          }

        Vue.prototype.$forceUpdate

        強制更新,刷新視圖數(shù)據(jù)沒有及時更新問題。通知當前實例對象是否存在 _watcher, 如果存在就直接 update()

        Vue.prototype.$forceUpdate = function ({
            const vm: Component = this
            if (vm._watcher) {
              vm._watcher.update()
            }
          }

        Vue.prototype.$destroy

        • 判斷是否開始銷毀,是就直接放回
        • callHook(vm, 'beforeDestroy') 調用 beforeDestroy
        • 設置 _isBeingDestroyedtrue
        • 移除自身
        • 銷毀 watcher
        • 移除 data.__ob__
        • 設置 _isDestroyed  為 true
        • callHook(vm, 'destroyed') 調用 destroyed
        • 解綁所有監(jiān)聽事件
        • 移除 vm.$el.vue = null
        • 移除 vm.$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

        掛載組件

        • 掛載之前會先調用 callHook(vm, 'beforeMount')
        • 更新組件
        updateComponent = () => {
          vm._update(vm._render(), hydrating)
        }
        • 依賴收集監(jiān)聽
        • 掛載
        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)
            }
          }

          // we set this to vm._watcher inside the watcher's constructor
          // since the watcher's initial patch may call $forceUpdate (e.g. inside child
          // component's mounted hook), which relies on vm._watcher being already defined
          new Watcher(vm, updateComponent, noop, {
            before() {
              if (vm._isMounted && !vm._isDestroyed) {
                callHook(vm, 'beforeUpdate')
              }
            }
          }, true /* isRenderWatcher */)
          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
        }

        updateChildComponent

        更新子組件之前,我們需要做以下處理

        • 拿到 parentVnode.data.scopedSlots
        • 拿到 vm.$scopedSlots
        • 判斷是否具有動態(tài) scopedSlots
        • 處理強制刷新操作 needsForceUpdate
        • 保存 parentVnode
        • 更新 _vnode.parent
        • 更新 attrs
        • 更新 listeners
        • 更新 props

        源碼如下:

        export function updateChildComponent(
          vm: Component,
          propsData: ?Object,
          listeners: ?Object,
          parentVnode: MountedComponentVNode,
          renderChildren: ?Array<VNode>
        {
          if (process.env.NODE_ENV !== 'production') {
            isUpdatingChildComponent = true
          }

          // determine whether component has slot children
          // we need to do this before overwriting $options._renderChildren.

          // check if there are dynamic scopedSlots (hand-written or compiled but with
          // dynamic slot names). Static scoped slots compiled from template has the
          // "$stable" marker.
          const newScopedSlots = parentVnode.data.scopedSlots
          const oldScopedSlots = vm.$scopedSlots
          const hasDynamicScopedSlot = !!(
            (newScopedSlots && !newScopedSlots.$stable) ||
            (oldScopedSlots !== emptyObject && !oldScopedSlots.$stable) ||
            (newScopedSlots && vm.$scopedSlots.$key !== newScopedSlots.$key)
          )

          // Any static slot children from the parent may have changed during parent's
          // update. Dynamic scoped slots may also have changed. In such cases, a forced
          // update is necessary to ensure correctness.
          const needsForceUpdate = !!(
            renderChildren ||               // has new static slots
            vm.$options._renderChildren ||  // has old static slots
            hasDynamicScopedSlot
          )

          vm.$options._parentVnode = parentVnode
          vm.$vnode = parentVnode // update vm's placeholder node without re-render

          if (vm._vnode) { // update child tree's parent
            vm._vnode.parent = parentVnode
          }
          vm.$options._renderChildren = renderChildren

          // update $attrs and $listeners hash
          // these are also reactive so they may trigger child update if the child
          // used them during render
          vm.$attrs = parentVnode.data.attrs || emptyObject
          vm.$listeners = listeners || emptyObject

          // update props
          if (propsData && vm.$options.props) {
            toggleObserving(false)
            const props = vm._props
            const propKeys = vm.$options._propKeys || []
            for (let i = 0; i < propKeys.length; i++) {
              const key = propKeys[i]
              const propOptions: any = vm.$options.props // wtf flow?
              props[key] = validateProp(key, propOptions, propsData, vm)
            }
            toggleObserving(true)
            // keep a copy of raw propsData
            vm.$options.propsData = propsData
          }

          // update listeners
          listeners = listeners || emptyObject
          const oldListeners = vm.$options._parentListeners
          vm.$options._parentListeners = listeners
          updateComponentListeners(vm, listeners, oldListeners)

          // resolve slots + force update if has children
          if (needsForceUpdate) {
            vm.$slots = resolveSlots(renderChildren, parentVnode.context)
            vm.$forceUpdate()
          }

          if (process.env.NODE_ENV !== 'production') {
            isUpdatingChildComponent = false
          }
        }

        activateChildComponent

        激活子組件

        • 判斷是否直接激活
        • 循環(huán)激活 vm.$children
        • 調用 callHook(vm, 'activated')
        export function activateChildComponent(vm: Component, direct?: boolean{
          if (direct) {
            vm._directInactive = false
            if (isInInactiveTree(vm)) {
              return
            }
          } else if (vm._directInactive) {
            return
          }
          if (vm._inactive || vm._inactive === null) {
            vm._inactive = false
            for (let i = 0; i < vm.$children.length; i++) {
              activateChildComponent(vm.$children[i])
            }
            callHook(vm, 'activated')
          }
        }

        deactivateChildComponent

        不激活組件

        • 判斷是否是直接不激活
        • 循環(huán)不激活 vm.$children
        • 調用 callHook(vm, "deactivated")
        export function deactivateChildComponent(vm: Component, direct?: boolean{
          if (direct) {
            vm._directInactive = true
            if (isInInactiveTree(vm)) {
              return
            }
          }
          if (!vm._inactive) {
            vm._inactive = true
            for (let i = 0; i < vm.$children.length; i++) {
              deactivateChildComponent(vm.$children[i])
            }
            callHook(vm, 'deactivated')
          }
        }

        callHook

        通過看源碼我發(fā)現(xiàn)子組件竟然可以這樣寫生命周期

        <com-a hook:updated="updatedEvent"></com-a>
        • 先入棧操作
        • 拿到 options.hook
        • 處理錯誤問題
        • vm.$emit('hook:' + hook)
        • 出棧操作
        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()
        }


        瀏覽 70
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产又黄又硬又粗 | 乳色吐息无删减版 | 国产尤物在线观看 | 亚洲黄网在线 | 欧美成人三级片 | 亚洲国产片在线观看 | 在线观看内射视频 | 中国老太卖婬HD大全免费看 | 包你爽综合网 | yinsebobo |