欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入了解Vue之組件的生命周期流程

 更新時間:2023年05月22日 09:57:42   作者:_只要平凡  
每個Vue實例在創(chuàng)建時都要經過一系列初始化,?例如設置數據監(jiān)聽、編譯模板、將實例掛載到DOM并在數據變化時更新DOM等,同時,?也會運行一些叫作生命周期鉤子的函數,接下來讓我們一起來探索Vue實例被創(chuàng)建時都經歷了什么,感興趣的同學跟著小編一起來探討吧

生命周期流程圖

每個Vue實例在創(chuàng)建時都要經過一系列初始化, 例如設置數據監(jiān)聽、編譯模板、將實例掛載到DOM并在數據變化時更新DOM等. 同時, 也會運行一些叫作生命周期鉤子的函數, 這給了我們在不同階段添加自定義代碼的機會. 接下來讓我們一起來探索Vue實例被創(chuàng)建時都經歷了什么.

生命周期圖示(貼自vue官網)

new Vue()被調用時發(fā)生了什么

想要了解new Vue()被調用時發(fā)生了什么, 我們需要知道在Vue構造函數中實現了哪些邏輯。具體代碼如下:

function Vue (options) {
    if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue)) {
        warn('Vue is a constructor and should be called with `new` keyword')
    }
    this._init(options)
}
export default Vue

從上面代碼我們可以看到調用了_init函數來執(zhí)行生命周期的初始化流程,那么this._init是在哪里定義的,內部原理是怎樣的呢?

_init方法的定義

import { initMixin } from './init'
function Vue (options) {
    if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue)) {
        warn('Vue is a constructor and should be called with `new` keyword')
    }
    this._init(options)
}
initMixin(Vue)
export default Vue

將init.js文件導出的initMixin函數引入后,通過調用initMixin函數向Vue構造函數的原型中掛載一些方法。initMixin方法的實現代碼如下:

export function initMixin (Vue) {
    Vue.prototype._init = function (options) {
        //
    }
}

_init方法的內部原理

Vue.prototype._init = function (options) {
    const vm = this
    // uid初始化值為0
    vm._uid = uid++
    let startTag, endTag
    if (__DEV__ && config.performance && mark) {
        startTag = `vue-perf-start:${vm._uid}`
        endTag = `vue-perf-end:${vm._uid}`
        mark(startTag)
    }
    // vue實例標志
    vm._Vue = true
    // 避免observed
    vm.__v_skip = true
    vm._scope = new EffectScope(true /* detached */)
    vm._scope._vm = true
    if (options && options._isComponent) {
      initInternalComponent(vm, options as any)
    } else {
       // 合并構造函數配置和參數配置
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor as any),
        options || {},
        vm
      )
    }
    if (__DEV__) {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    // 初始化生命周期相關
    initLifecycle(vm)
    // 初始化事件相關
    initEvents(vm)
    // 初始化render相關
    initRender(vm)
    // 調用beforeCreate鉤子
    callHook(vm, 'beforeCreate', undefined, false)
    // 初始化inject
    initInjections(vm)
    // 初始化狀態(tài)相關
    initState(vm)
    // 初始化provide
    initProvide(vm)
    // 調用created鉤子
    callHook(vm, 'created')
    if (__DEV__ && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }
    // 如果有配置el選項 自動調用mount 否則需要手動調用mount
    if (vm.$options.el) {
        vm.$mount(vm.$options.el)
    }
}

從_init方法的源碼實現, 我們可以畫出new Vue的主要流程圖:

接下來我們來看下_init內部幾個初始化方法的源碼實現:

initLifecycle

function initLifecycle (vm) {
    const options = vm.$options
    let parent = options.parenet
    if (parent && !options.abstract) {
        // 如果為抽象組件 繼續(xù)往上找
        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._provided = parent ? parent._provided : Object.create(null)
    vm._watcher = null
    vm._inactive = null
    vm._directInactive = false
    vm._isMounted = false
    vm._isDestroyed = false
    vm._isBeingDestroyed = false
}

initEvents

function initEvents (vm) {
    vm._events = Object.create(null)
    vm._hasHookEvent = false
    const listeners = vm.$options._parentListeners
    if (listeners) {
        updateComponentListeners(vm, listeners)
    }
}

initRender

function initRender(vm) {
  vm._vnode = null
  vm._staticTrees = null
  const options = vm.$options
  const parentVnode = (vm.$vnode = options._parentVnode!)
  const renderContext = parentVnode && (parentVnode.context as Component)
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  vm.$scopedSlots = parentVnode
    ? normalizeScopedSlots(
        vm.$parent!,
        parentVnode.data!.scopedSlots,
        vm.$slots
      )
    : emptyObject
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
  const parentData = parentVnode && parentVnode.data
  if (__DEV__) {
    defineReactive(
      vm,
      '$attrs',
      (parentData && parentData.attrs) || emptyObject,
      () => {
        !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
      },
      true
    )
    defineReactive(
      vm,
      '$listeners',
      options._parentListeners || emptyObject,
      () => {
        !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
      },
      true
    )
  } else {
    defineReactive(
      vm,
      '$attrs',
      (parentData && parentData.attrs) || emptyObject,
      null,
      true
    )
    defineReactive(
      vm,
      '$listeners',
      options._parentListeners || emptyObject,
      null,
      true
    )
  }
}

initInjections

function initInjections(vm: Component) {
  const result = resolveInject(vm.$options.inject, vm)
  if (result) {
    toggleObserving(false)
    Object.keys(result).forEach(key => {
      if (__DEV__) {
        defineReactive(vm, key, result[key], () => {
          warn(
            `Avoid mutating an injected value directly since the changes will be ` +
              `overwritten whenever the provided component re-renders. ` +
              `injection being mutated: "${key}"`,
            vm
          )
        })
      } else {
        defineReactive(vm, key, result[key])
      }
    })
    toggleObserving(true)
  }
}

function resolveInject(
  inject: any,
  vm: Component
): Record<string, any> | undefined | null {
  if (inject) {
    const result = Object.create(null)
    const keys = hasSymbol ? Reflect.ownKeys(inject) : Object.keys(inject)
    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      if (key === '__ob__') continue
      const provideKey = inject[key].from
      if (provideKey in vm._provided) {
        result[key] = vm._provided[provideKey]
      } else if ('default' in inject[key]) {
        const provideDefault = inject[key].default
        result[key] = isFunction(provideDefault)
          ? provideDefault.call(vm)
          : provideDefault
      } else if (__DEV__) {
        warn(`Injection "${key as string}" not found`, vm)
      }
    }
        return result
  }
}

initState

function initState (vm) {
    const opts = vm.$options
    
    if (opts.props) {
        initProps(vm, opts.props)
    }
    
    // 組合式api
    initSetup(vm)
    
    if (opts.methods) {
        initMethods(vm, opts.methods)
    }
    
    if (ops.data) {
        initData(vm, opts.data)
    } else {
        const ob = observe(vm._data = {})
        ob && ob.vmCount++
    }
    
    if (opts.compouted) {
        initComputed(vm, opts.computed)
    }
    
    // Firefox Object.prototype有watch方法 nativeWatch = {}.watch
    if (opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch)
    }
}

function initProps(vm, propsOptions) {
  const propsData = vm.$options.propsData || {}
  const props = (vm._props = shallowReactive({}))
  // 用數組保存props的key 方便便利props
  const keys: string[] = (vm.$options._propKeys = [])
  const isRoot = !vm.$parent
  if (!isRoot) {
    toggleObserving(false)
  }
  for (const key in propsOptions) {
    keys.push(key)
    const value = validateProp(key, propsOptions, propsData, vm)
    if (__DEV__) {
      const hyphenatedKey = hyphenate(key)
      if (
        isReservedAttribute(hyphenatedKey) ||
        config.isReservedAttr(hyphenatedKey)
      ) {
        warn(
          `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
          vm
        )
      }
      defineReactive(props, key, value, () => {
        if (!isRoot && !isUpdatingChildComponent) {
          warn(
            `Avoid mutating a prop directly since the value will be ` +
              `overwritten whenever the parent component re-renders. ` +
              `Instead, use a data or computed property based on the prop's ` +
              `value. Prop being mutated: "${key}"`,
            vm
          )
        }
      })
    } else {
      defineReactive(props, key, value)
    }
    if (!(key in vm)) {
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)
}

function initData(vm) {
  let data: any = vm.$options.data
  data = vm._data = isFunction(data) ? getData(data, vm) : data || {}
  if (!isPlainObject(data)) {
    data = {}
    __DEV__ &&
      warn(
        'data functions should return an object:\n' +
          'https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
        vm
      )
  }
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
    if (__DEV__) {
      if (methods && hasOwn(methods, key)) {
        warn(`Method "${key}" has already been defined as a data property.`, vm)
      }
    }
    if (props && hasOwn(props, key)) {
      __DEV__ &&
        warn(
          `The data property "${key}" is already declared as a prop. ` +
            `Use prop default value instead.`,
          vm
        )
    } else if (!isReserved(key)) {
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  const ob = observe(data)
  ob && ob.vmCount++
}

function initMethods(vm, methods: Object) {
  const props = vm.$options.props
  for (const key in methods) {
    if (__DEV__) {
      if (typeof methods[key] !== 'function') {
        warn(
          `Method "${key}" has type "${typeof methods[
            key
          ]}" in the component definition. ` +
            `Did you reference the function correctly?`,
          vm
        )
      }
      if (props && hasOwn(props, key)) {
        warn(`Method "${key}" has already been defined as a prop.`, vm)
      }
      if (key in vm && isReserved(key)) {
        warn(
          `Method "${key}" conflicts with an existing Vue instance method. ` +
            `Avoid defining component methods that start with _ or $.`
        )
      }
    }
    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
  }
}

function initWatch(vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    if (isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}

function createWatcher(
  vm: Component,
  expOrFn: string | (() => any),
  handler: any,
  options?: Object
) {
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
}

initProvides

function provide<T>(key: InjectionKey<T> | string | number, value: T) {
  if (!currentInstance) {
    if (__DEV__) {
      warn(`provide() can only be used inside setup().`)
    }
  } else {
    resolveProvided(currentInstance)[key as string] = value
  }
}

export function resolveProvided(vm: Component): Record<string, any> {
  const existing = vm._provided
  const parentProvides = vm.$parent && vm.$parent._provided
  if (parentProvides === existing) {
    return (vm._provided = Object.create(parentProvides))
  } else {
    return existing
  }
}

mount實現

到這里, 初始化基本完成. 從前面_init方法的實現, 我們可以看到初始化之后會執(zhí)行mount, 代碼如下:

// 如果有配置el選項 自動調用mount 否則需要手動調用mount
if (vm.$options.el) {
    vm.$mount(vm.$options.el)
}

接下來我們來看下vm.$mount的具體實現:

function query (el) {
    if (typeof el === 'string') {
        const selected = document.querySelector(el)
        if (!selected) {
            __DEV__ && warn('Cannot find element: ' + el)
            return docuemnt.createElement('div')
        }
        return selected
    } else {
    return el
}
function cached (fn) {
    // 模板緩存
    const cache = Object.create(null)
    return function (str) {
        const hit = cache[str]
        return hit || (cache[str] = fn(str))
    }
}
const idToTemplate = cached(id => {
    const el = query(id)
    return el && el.innerHTML
})
function getOuterHTML (el) {
    if (el.outerHTML) {
        return el.outerHTML
    } else {
        const container = document.createElement('div')
        container.appendChild(el.cloneNode(true))
        return container.innerHTML
    }
}
Vue.prototype.$mount = function (el, hydrating) {
    el = el && inBrower ? query(el) : undefined
    return mountComponent(this, el, hydrating)
}
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (el, hydrating) {
    el = el && query(el)
    if (el === document.body || el === document.documentElement) {
        __DEV__ &&
            warn(`Do not mount Vue to <html> or <body> - mount to normal elements instead.`)
        return this
    }
    const options = this.$options
    // 如果options沒有render 使用template屬性
    if (!options.render) {
        let template = options.template
        // template屬性優(yōu)先使用
        if (template) {
            if (typeof template === 'string') {
                if (template.charAt(0) === '#') {
                    template = idToTemplate(template)
                    if (__DEV__ && !template) {
                    warn(
                      `Template element not found or is empty: ${options.template}`,
                      this
                    )
                  }
                }
            } else if (template.nodeType) {
                template = template.innerHTML
            } else {
                if (__DEV__) {
                  warn('invalid template option:' + template, this)
                }
                return this
            }
        } else if (el) { // template屬性不存在 再使用el屬性
            template = getOuterHTML(el)
        }
        if (template) {
            if (__DEV__ && config.performance && mark) {
                mark('compile')
            }
            const { render, staticRenderFns } = compileToFunctions(
                template,
                {
                  outputSourceRange: __DEV__,
                  shouldDecodeNewlines,
                  shouldDecodeNewlinesForHref,
                  delimiters: options.delimiters,
                  comments: options.comments
                },
                this
          )
          options.render = render
          options.staticRenderFns = staticRenderFns
          if (__DEV__ && config.performance && mark) {
            mark('compile end')
            measure(`vue ${this._name} compile`, 'compile', 'compile end')
          }
        }
    }
    return mount.call(this, el, hydrating)
}

從$mount源碼中我們可以知道m(xù)ount的核心是mountComponent函數, 下面我們來看下mountComponent的實現:

function mountComponent (vm, el, hydrating) {
    vm.$el = el
    if (!vm.$options.render) {
        vm.$options.render = createEmptyNode
        if (__DEV__) {
            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
    if (__DEV__ && 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)
    }
  }
  const watcherOptions: WatcherOptions = {
    before() {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }

  if (__DEV__) {
    watcherOptions.onTrack = e => callHook(vm, 'renderTracked', [e])
    watcherOptions.onTrigger = e => callHook(vm, 'renderTriggered', [e])
  }
  new Watcher(
    vm,
    updateComponent,
    noop,
    watcherOptions,
    true /* isRenderWatcher */
  )
  hydrating = false
  const preWatchers = vm._preWatchers
  if (preWatchers) {
    for (let i = 0; i < preWatchers.length; i++) {
      preWatchers[i].run()
    }
  }
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

mountComponent代碼實現中會實例化一個Watcher對象, 這里主要有兩個作用:

  • 初始化的時候會執(zhí)行expOrFn, 也就是updateComponent.
  • 當vm實例中監(jiān)測的數據發(fā)生變化時會執(zhí)行updateComponent.
updateComponent = () => {
  vm._update(vm._render(), hydrating)
}

我們接著來看下vm._update的實現:

Vue.prototype._update = function (vnode, hydrating) {
    const vm = this
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode
    if (!prevVnode) {
      // 初始化渲染
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false)
    } else {
      // 組件更新時
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    restoreActiveInstance()
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    let wrapper = vm
    while (
      wrapper &&
      wrapper.$vnode &&
      wrapper.$parent &&
      wrapper.$vnode === wrapper.$parent._vnode
    ) {
      wrapper.$parent.$el = wrapper.$el
      wrapper = wrapper.$parent
    }
  }

我們可以看到組件初始化渲染時會執(zhí)行vm.__patch__方法, vm.__patch__的具體實現本章就不細說了, 后面在虛擬DOM章節(jié)中再一起學習下, 這里就大概說下__patch__方法主要做了什么:

  • 循環(huán)遍歷vm.children, 執(zhí)行createElm方法
  • createElm方法執(zhí)行時如果當前child元素是組件則創(chuàng)建組件, 并執(zhí)行組件init方法和mount方法(就是重新走一遍前面流程), 然后插入當前元素, 執(zhí)行組件mounted鉤子

最后

以上就是vue組件生命周期主要流程, 從源碼實現中, 我們可以知道父子組件初始化生命周期鉤子執(zhí)行順序:

beforeCreate(父) -> created(父) -> beforeMount(父) -> beforeCreate(子) -> created(子) -> beforeMount(子) -> mounted(子) -> mounted(父)

以上就是深入了解Vue之組件的生命周期流程的詳細內容,更多關于Vue 生命周期的資料請關注腳本之家其它相關文章!

相關文章

最新評論