<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          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

          設(shè)置激活的組件實例對象,是因為存在 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);
              }
              /*設(shè)置當前的組件$parent指向父級組件*/
              vm.$parent = parent;
              vm.$root = parent ? parent.$root : vm;

              /*設(shè)置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設(shè)置了一些屬性

          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') 調(diào)用 beforeDestroy
          • 設(shè)置 _isBeingDestroyedtrue
          • 移除自身
          • 銷毀 watcher
          • 移除 data.__ob__
          • 設(shè)置 _isDestroyed  為 true
          • callHook(vm, 'destroyed') 調(diào)用 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

          掛載組件

          • 掛載之前會先調(diào)用 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
          • 調(diào)用 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
          • 調(diào)用 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
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产777视频 | 日本中文字幕手机在线 | 美女尿口无遮挡 | 蜜臀AV一区二区三区有限公司 | 午夜综合在线 |