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

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è)置 _isBeingDestroyed為true移除自身 銷毀 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()
}
評論
圖片
表情
