美文网首页
生命周期

生命周期

作者: 达文西_Huong | 来源:发表于2020-06-17 10:36 被阅读0次

    关于Vue的声明周期

    image

    下面讲解以下上面的解析

    总的来说,Vue的生命周期分为以下八个阶段

     beforeCreate  //实例创建前
     created       //实例创建完成
     beforeMount   //挂载前
     mounted       //挂载完成
     beforeUpdate  //更新前
     updated       //更新完成
     beforeDestory //销毁前
     destoryed     //销毁完成
    
    1.beforeCreate

    这个钩子是new Vue()之后触发的第一个钩子,在当前阶段中data、methods、computed以及watch上的数据和方法都不能被访问

    2.created

    这个钩子在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里改数据是不会触发updated函数。可以做一些初始数据的获取。
    注意请求数量不宜过多,否则会造成白屏时间过长。在当前阶段无法与DOM进行交互。如果你一定要进行交互,可以通过vm.$nextTick来访问DOM

    3.beforeMount

    这个钩子发生在挂载之前,在这之前template模板已导入渲染函数编译。而当前阶段虚拟DOM已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发updated。

    4.mounted

    这个钩子在挂载完成后发生,在当前阶段,真实的DOM挂载完毕,数据完成双向绑定,可以访问到DOM节点,使用$ref属性对DOM进行操作。也可以向后台请求,拿取相应的数据

    5.beforeUpdate

    这个钩子发生在更新前,也就是响应式数据发生更新,虚拟DOM重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重渲染。

    6.updated

    这个钩子发生在更新完成之后,当前阶段组件DOM已完成更新。要注意的是避免在此期间更改数据,否则会可能导致无限循环更新

    7.beforedDestory

    这个钩子发生在实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,例如清除定时器。

    8.destroyed

    这个钩子发生在实例销毁之后,这个时候只剩下dom空壳。组件已被拆解,数据绑定被卸除,监听被移除,子实例也通通被消除。


    以上就是生命周期的基础介绍,还有一些需要注意的点:

    1. 我门上文提到的created阶段的ajax请求与mounted请求的区别。前者页面的视图还未出现,如果请求的信息过多,页面会长时间处于白屏状态

    2. 除了beforeCreate和created钩子之外,其他钩子均在服务端渲染期间不被调用。

    3. 上文提到的,在updated的时候千万不要去修改data里面赋值的数据,否则会导致死循环

    4. Vue的所有生命周期函数都是自动绑定到this的上下文上。所以,你在这里使用箭头函数的话,就会导致this指向的是父级作用域,会导致报错。下面介绍源码的时候会细说。

    源码解读

    因为Vue的源码部分包含很多内容,本文只选取生命周期相关的关键性代码进行解析。

    附上Vue源码的学习连接,推荐也学习其他的源码部分:https://ustbhuangyi.github.io/vue-analysis/prepare/

    我们先来从源码中来解答上文提到的需要注意的四点。(以下所有代码都有所删减,省略部分用...表示)

        // src/core/instance/lifecycle.js
        // callhook 函数的功能就是在当前vue组件实例中,调用某个生命周期钩子注册的所有回调函数。
        // vm:Vue实例
        // hook:生命周期名字
        export function callHook(vm:Component, hook:string){
            pushTarget()
            const handlers = vm.$options[hook]
            // 初始化合并 options 的过程,将各个生命周期函数合并到 options 里
            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()
        }
    
        // src/core/util/error.js
        export function invokeWithErrorHandling(
            handler:Function,
            context: any,
            args:null|any[],
            vm:any,
            info:string
        ){
            let res
            try{
                res = args? handler.apply(context,args) : handler.call(context)
                if(res && !res._isVue && isPromise(res) && !res._handled){
                    res._handled = true
                }
            }catch(e){
                handleError(e, vm, info)
            }
            return res
        }
    

    我们从上面的代码中可以看到,callHook中调用了invokeWithErrorHandling方法。在invokeWithErrorHandling方法中,使用了apply和call去改变this的指向。而在箭头函数中,this的指向是无法改变的,所以我们在编写生命周期函数的时候不能使用箭头函数

    下面开始介绍生命周期

    1.beforeCreate和created

        // src/core/instance/init
        export function initMixin(Vue:Class<Component>){
            Vue.prototype._init = function(options?:Object){
                const vm:Component = this
                ...
                // 合并选项部分已省略
    
                initLifecycle(vm)
                // 主要就是给vm对象添加了$parent, $root,$children 属性,以及其他的生命周期相关标识
                initEvents(vm)    // 初始化事件相关属性
                initRender(vm)    // vm 添加了一些虚拟DOM,slot等相关的属性和方法
                callHook(vm,'beforeCreate') // 调用beforeCreate 钩子
                // 下面 initInjections(vm) 和 initProvide(vm) 两个配套使用,用于将父组件 _provided 中定义的值,通过inject 注入到子组件,且这些属性不会被观察
                initInjections(vm)
                initState(vm)     // props,methods,data,watch,computed 等数据初始化
                initProvide(vm)
                callHook(vm, 'created')     // 调用 created 钩子
            }
        }
        
        // src/core/instance/state
        export function initState(vm:Component) {
            vm._watchers = []
            const opts = vm.$options
            if(opts.props) initProps(vm, props)
            if(opts.methods) initMethods(vm, methods)
            if(opts.data){
                initData(vm)
            }else{
                observe(vm._data = {}, true)
            }
            if(opts.computed) initComputed(vm, conputed)
            if(opts.watch && opts.watch !== nativeWatch) {
                initWatch(vm, opts.watch)
            }
        }
    

    我们可以看到beforeCreate钩子调用是在initState之前的,而从上面的第二段代码我们可以看出initState的作用是对props,method,data,computed,watch等属性进行初始化的。

    通过阅读源码,我们可以清楚的看到,在beforeCreate钩子的时候,我们并没有对props,method,data,computed,watch上的数据的访问权限。在created中才可以

    2.beforeMount和mounted

        // mounteComponent 核心就是先实例化一个渲染watcher
        // 在它的回调函数中调用 updateComponent 方法
        // 两个核心方法 vm._render(生成虚拟Dom) 和 vm._update(映射到真实Dom)
        // src/core/instance/lifecycle
    
        export function mountComponent(
            vm:Component,
            el:?Element,
            hydrating?: boolean
        ):Component {
            vm.$el = el
            if(!vm.$options.render) {
                vm.$options.render = createEmptyVNode
                ...
            }
            callHook(vm, 'beforeMount')     // 调用 beforeMount 钩子
    
            let updateComponent
            if(process.env.NODE_ENV !== 'production' && config.performance && mark){
                updateComponent = () => {
                    // 将虚拟DOM映射到真实 DOM 的函数
                    // vm._update 之前会先调用 vm._render() 函数渲染 vNode
                    ...
                    const vnode = vm._render()
                    ...
                    vm._update(vnode, hydrating)
                }
            } else {
                updateComponent = () => {
                    vm._update(vm._render(), hydrating)
                }
            }
            new Watcher(vm, updateComponent, noop, {
                before(){
                    // 先判断是否 mounted 完成,并且没有被destoryed
                    if(vm._isMounted && !vm._isDestoryed) {
                        callHook(vm, 'beforeUpdate')
                    }
                }
            }, true)
    
            if(vm.$vnode == null){
                vm._isMounted = true
                callHook(vm, 'mounted')       // 调用mounted钩子
            }
    
            return vm
        }
    

    通过上面的代码,我们可以看出在执行 vm._render() 函数渲染VNode之前, 执行了beforeMounted 钩子函数,在执行完vm._update() 把VNode patch到真实Dom后,执行 mounted 钩子。也就明白了为什么知道 mounted 阶段才名正言顺的拿到DOM

    3.beforeUpdate和update

        // src/core/instance/lifecycle
        new Watcher(vm, updateComponent, noop, {
            before() {
                 if (vm._isMounted && !vm._isDestroyed) {
                    callHook(vm, 'beforeUpdate')  // 调用 beforeUpdate 钩子
                }
            }
        }, true)
    
        // src/core/observer/scheduler
        function callUpdateHooks(queue){
            let i = queue.length
            while (i--) {
                const watcher = queue[i]
                const vm = watcher.vm
                if(vm._watcher === watcher && vm._isMounted && vm._isDestoryed){
                    // 只有满足当前 watcher 为vm._watcher (也就是当前的渲染watcher)
                    // 以及组件已经 mounted 并且没有被destoryed 才会执行 updated 钩子函数
                    callHook(vm, 'updated') // 调用 updated 钩子
    
                }
            }
        }
    

    第一段代码就是在 beforeMount 和 mounted 钩子中间出现
    那么 watcher 中究竟做了些什么
    第二段代码的 callUpdateHooks 函数中什么时候才可以满足条件并执行 updated 呢
    我们来接着往下看

        // src/instance/observer/watcher.js
        export function class Watcher {
            ...
            constructor(
                vm: Component,
                expOrFn: String | Function,
                cb: Function,
                options?:?Object,
                // 在它的构造函数里会判断 isRenderWatcher,
                // 接着把当前 watcher 的实例赋值给 vm._watcher
                isRenderWatcher?: boolean
            ){
                // 还把当前 watcher 实例 push 到 vm._watchers 中
                // vm._watcher 是专门用来监听 vm 上数据变化然后重新渲染的
                // 所以它是一个渲染相关的 watcher,因此在 callUpdateHooks 函数中
                // 只有 vm._watcher 的回调执行完之后,才会执行updated 钩子函数
                this.vm = vm
                if(isRenderWatcher) {
                    vm._watcher = this
                }
                vm._watchers.push(this)
                ...
            }
        }
    

    看到这里,我们知道Vue是通过watcher来监听实例上的数据变化,进而控制渲染流程。

    4.beforeDestory 和 destoryed

        // src/core/instance/lifecycle.js
        // 在$destory 的执行过程中,它会执行 vm._patch_(vm._vnode, null)
        // 触发它子组件的销毁钩子函数,这样一层层的递归调用
        // 所以 destory 钩子函数执行顺序是先 子后父,和mounted 过程一样。
        Vue.prototype.$destory = function() {
            const vm: Component = this
            if(vm._isBeingDestoryed){
                return
            }
            callHook(vm, 'beforeDestory')  // 调用 beforeDestory 钩子
            vm._isBeingDestoryed = true
            // 一些销毁工作
            const parent = vm.$parent
            if(parent && !parent._isBeingDestoryed && !vm.$options.abstract) {
                remove(parent.$children, vm)
            }
            // 卸载 watchers
            if(vm._watcher){
                vm._watcher.teardown()
            }
            let i = vm._watchers.length
            while(i--){
                vm._watchers[i].teardown()
            }
            ...
            vm.isDestoryed = true
            // 调用当前 rendered tree 上的 destory 钩子
            // 发现子组件,会先去销毁子组件
            vm.__patch__(vm.vnode, null)
            callHook(vm, 'destoryed')   // 调用 destoryed 钩子
            // 关闭所有实例侦听器
            vm.$off()
            // 删除__vue__ 引用
            if(vm.$el) {
                vm.$el.__vue__ = null
            } 
            // 释放循环引用
            if(vm.$vnode) {
                vm.$vnode.parent = null
            }
        }
    

    通过上面的代码,我们了解了组件销毁阶段的拆卸过程,其中会执行一个patch函数。

    更多关于源码的细节,请阅读上文提到的源码链接。

    原文:https://mp.weixin.qq.com/s/6-WDKTyyDnkbw224WOEpjw

    相关文章

      网友评论

          本文标题:生命周期

          本文链接:https://www.haomeiwen.com/subject/ocvjxktx.html