?基础知识又得重新补习一下了
前情回顾
理解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`。
core/instance/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
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
及当前实例分别赋值给prevNode
和preActiveInstance
。如果没有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相关的内容。明天顺带着提一下吧。
最后说两句
- 动一动您发财的小手,
「点个赞吧」
- 动一动您发财的小手,
「点个在看」
- 都看到这里了,不妨
「加个关注」
- 不妨
「转发一下」
,好东西要记得分享