Vue(四)——组件生命周期、ref与$refs、nextTick

89 篇文章 7 订阅

一、组件生命周期

图示解析:

此图为vue官网提供的vue生命周期的整个过程:

  1. 组件最开始从new Vue()开始组件生成对象开始,当然也可以是从Vue.component()构建组件对象开始,两者没有多大区别。在 new Vue()时就相当于在执行构造函数,在构造函数中,即从组件创建到组件最后出现,并可以使用经历了很多过程;
  2. init Events & Lifecycel:初始化一些数据、事件、生命周期等,可以认为是属性初始化或工具初始化;
  3. 完成以后就会去执行beforeCreate函数;
  4. init injectiongs&reactivity :beforeCreate函数调用完成后,就会再去初始化注入依赖、数据拦截等;
  5. 完成后就会去执行created函数,即vue组件构建完成;以后所以环节已经将vue组件的所有核心内容准备好;
  6. 判断当前是否有el选项,有则继续判断是否有template选项;无则通过$mount(el)手动渲染组件;
  7. el和template都准备好以后,就会进去组件编译阶段(compile template into render function),对组件模块进行编译加载;
  8. 整个组件编译完后,产生了整个DOM节点后,会调用beforeMount()方法,
  9. 调用beforeMount()方法就会进行挂载,挂载到父级el选项中,并给vue的组件实例增加el选项;
  10. 整个挂载完成后,调用mounted(),进行组件的渲染。
  11. 到此为止从组件的首次初始化,首次渲染,再到首次挂载到页面中的整个过程,执行完成
  12. 最后进入一个循环阶段,称为更新阶段;组件挂载完成后并不是不做任何事了,而是不断去监听组件的各种数据和状态变化、更新
  13. 当数据发生改变时,就会触发beforeUpdate(),执行完成就,组件就会对DOM进行重新渲染(Virtual DOM re-render and patch)
  14. 渲染完成后执行update,重新更新组件
  15. 后续整个工作过程都是不断执行更新组件这个过程,直到进行组件销毁,此时就会触发beforeDestroy();
  16. 销毁时触发destroyed()方法真正销毁组件

 

组件生命周期指的是组件从创建到销毁的过程,在这个过程中的一些不同的阶段,vue 会调用指定的一些组件方法

基本生命周期函数有下面几个阶段:

  • 创建阶段
  • 挂载阶段
  • 更新阶段
  • 卸载阶段

每一个阶段都对应着 之前之后 两个函数

1.1创建阶段

1.1.1 beforeCreate()

初始化阶段,应用不多。

此时,只初始化了组件的时间和生命周期等基本的东西,还无法获取组件的data和el数据,此时这些都还没有准备好

1.1.2 created()

在实例创建完成后被立即调用,该阶段完成了data 中的数据的 observer,该阶段可以处理一些异步任务。

但是此时el还没有准备好(要一直到beforeMount()后才准备好),所以这个阶段只能去处理一些与DOM操作无关的事情

1.2挂载阶段

1.2.1 beforeMount()

在挂载开始之前被调用,应用不多。

此时已经可以访问数据和el等。注意:此时数据和el都已解析完成,但是还没有挂载到页面中,如果此时要去获取元素宽高等属性不一定能获取到。

如果有子组件,则根组件在执行完beforeMount()后,会先将子组件从beforeCreate()执行到mounted()渲染完成后,再执行根组件的mounted()函数。

1.2.2 mounted()

该阶段执行完了模板解析,以及挂载。同时组件根组件元素被赋给了 $el 属性,该阶段可以通过 DOM 操作来对组件内部元素进行处理了。

1.3更新阶段

1.3.1 beforeUpdate()

数据更新时调用,但是还没有对视图进行重新渲染,这个时候,可以获取视图更新之前的状态

1.3.2 updated()

由于数据的变更导致的视图重新渲染,可以通过 DOM 操作来获取视图的最新状态

1.4 卸载阶段

1.4.1 beforeDestroy()

实例销毁之前调用,移除一些与组件无关的,不必要的冗余数据,比如定时器。

与组件有关的,如prop数据等,会自动销毁

1.4.2 destroyed()

Vue 实例销毁后调用。

1.4.3 errorCaptured()

当捕获一个来自子孙组件的错误时被调用,此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

由自己捕获并处理错误。

最后使用<template>内置组件判断有错误和没有发生错误时的处理方式:

<template v-if="hasError">
            <h4>有错误发生了</h4>
        </template>
            <template v-else>
            <kkb-component v-if="show" :t="title"></kkb-component>
        </template>

1.5生命周期示例

需求

监控子组件和根组件的生命周期执行过程;

根组件中按钮点击隐藏时,监控根组件和子组件生命周期的执行过程;(根组件的更新子组件同时也会更新)

如果想要更新子组件,改变根组件t的值,子组件接收数据t后就会进行更新;

错误处理;

步骤分析

创建一个kkbComponent组件,组件中接收到父级传入的数据t,组件模板只有显示功能;

给子组件分别挂载了beforeCreate(),created(), beforeMount(),mounted(),beforeUpdate(),updated(),beforeDestory(),destoryed()所有生命周期的函数;

在根组件实例里绑定子组件,并绑定自己的所有生命周期函数;

根组件中按钮点击隐藏时,监控根组件(更新)和子组件(销毁(v-if))生命周期的执行过程;

根组件中按钮再次点击隐藏时,监控根组件(更新)和子组件(创建到销毁)生命周期的执行过程;

如果想要更新子组件,改变根组件t的值,子组件接收数据t后就会进行更新(此时子组件和根组件都会执行更新阶段函数);

因为使用的v-if所以点击隐藏时,就会执行销毁方法;

故意使用错误的数据输出,{{t.a.b}}形成错误,进行错误处理;

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <h1>{{title}}</h1>
        <button @click="show=!show">隐藏</button>
        <hr>
        <template v-if="hasError">
            <h4>有错误发生了</h4>
        </template>
        <template v-else>
            <kkb-component v-if="show" :t="title"></kkb-component>
        </template>
    </div>
    <script src="./js/vue.js"></script>
    <script>
        const kkbComponent = {
            props: ['t'],
            template: `
                <div>
                    <h1>kkbComponent - {{t.a.b}}</h1>
                </div>
            `,
            beforeCreate() {
                console.log('kkbComponent:beforeCreate');
                console.log('data', this.$data);
                console.log('el', this.$el);
                console.log('='.repeat(100));
            },
            created() {
                console.log('kkbComponent:created');
                console.log('data', this.$data);
                console.log('el', this.$el);
                console.log('='.repeat(100));
            },
            beforeMount() {
                console.log('kkbComponent:beforeMount');
                console.log('data', this.$data);
                console.log('el', this.$el);
                console.log('='.repeat(100));
            },
            mounted() {
                console.log('kkbComponent:mounted');
                console.log('data', this.$data);
                console.log('el', this.$el);
                console.log('='.repeat(100));
            },
            beforeUpdate() {
                console.log('kkbComponent:beforeUpdate');
                console.log('props', this.$props);
                console.log('='.repeat(100));
            },
            updated() {
                console.log('kkbComponent:updated');
                console.log('='.repeat(100));
            },
            beforeDestroy() {
                console.log('kkbComponent:beforeDestroy');
                console.log('this', this);
                console.log('='.repeat(100));
            },
            destroyed() {
                console.log('kkbComponent:destroyed');
                console.log('this', this);
                console.log('='.repeat(100));
            }
        }

        let app = new Vue({
            el: '#app',
            data: {
                title: '开课吧',
                // 控制是否点击了隐藏按钮
                show: true,
                // 控制是否捕捉到错误
                hasError: false
            },
            components: {
                'kkb-component': kkbComponent
            },

            beforeCreate() {
                console.log('beforeCreate');
                console.log('data', this.$data);
                console.log('el', this.$el);
                console.log('='.repeat(100));
            },
            created() {
                console.log('created');
                console.log('data', this.$data);
                console.log('el', this.$el);
                console.log('='.repeat(100));
            },
            beforeMount() {
                console.log('beforeMount');
                console.log('data', this.$data);
                console.log('el', this.$el);
                console.log('='.repeat(100));
            },
            // 如果有子组件,则根组件在执行完beforeMount()后,会先将子组件从beforeCreate()执行到mounted()渲染完成后,再执行根组件的mounted()函数。
            mounted() {
                console.log('mounted');
                console.log('data', this.$data);
                console.log('el', this.$el);
                console.log('='.repeat(100));
            },
            beforeUpdate() {
                console.log('beforeUpdate');
                console.log('props', this.$props);
                console.log('='.repeat(100));
            },
            updated() {
                console.log('updated');
                console.log('='.repeat(100));
            },
            beforeDestroy() {
                console.log('beforeDestroy');
                console.log('this', this);
                console.log('='.repeat(100));
            },
            destroyed() {
                console.log('destroyed');
                console.log('this', this);
                console.log('='.repeat(100));
            },
            errorCaptured(err, vm, info) {
                console.log('errorCaptured');
                console.log(err, vm, info);
                console.log('='.repeat(100));
                this.hasError = true;
                return false;
            }
        });
    </script>
</body>

</html>

二、ref与$refs

如果我们希望获取组件节点,进行 DOM 相关操作,可以通过 ref$refs 来完成。

如果子组件中有ref属性,则父级组件可以通过$refs属性获取子组件实例对象及其内部方法,并对其进行DOM操作。

2.1 ref

给元素或组件添加 ref 属性,则该元素或组件实例对象将被添加到当前组件实例对象的 $refs 属性下面

2.2 $refs

该属性的是一个对象,存储了通过 ref 绑定的元素对象或者组件实例对象

2.3 ref和$refs示例

需求:

  1. 子组件自己点击隐藏和显示;
  2. 根组件,点击按钮获取整个div高度(加ref属性,通过$refs获取);
  3. 根组件控制子组件的隐藏和显示(根组件中获取子组件ref属性)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <h1>{{title}}</h1>
        <button @click="getBoxHeight">获取 box 的高度</button>
        <button @click="getKkbCompoment">获取自定义组件的实例及其内部方法</button>
        <hr>
        <div ref="box">
            这是内容<br>这是内容<br>这是内容<br>这是内容<br>这是内容<br>
        </div>
        <hr>
        <!-- 1.子组件自己控制隐藏 -->
        <!-- <kkb-component :t="title"></kkb-component> -->
        <!-- 3.获取自定义组件的实例及其内部方法:根组件控制子组件隐藏 -->
        <kkb-component ref="kkb" :t="title"></kkb-component>
    </div>
    <script src="./js/vue.js"></script>
    <script>
        const kkbComponent = {
            props: ['t'],
            data() {
                return {
                    isShow: true
                }
            },
            template: `
                <div>
                    <button @click="hide">隐藏</button>
                    <h1 v-if="isShow">kkbComponent - {{t}}</h1>
                </div>
            `,
            methods: {
                hide() {
                    this.isShow = !this.isShow;
                }
            }
        }

        let app = new Vue({
            el: '#app',
            data: {
                title: '开课吧'
            },
            components: {
                'kkb-component': kkbComponent
            },
            mounted(){
                // 根组件mounted方法执行之前,子组件会先执行mounted方法渲染DOM结构,所以在根组件mounted方法中已经能调用到this.$refs.kkb
                console.log(this.$refs.kkb);
                
            },
            methods: {
                getBoxHeight() {
                    // 2.根组件获取自身组件下div的高度
                    console.log( this.$refs.box.clientHeight );
                },
                getKkbCompoment(){
                    // 获取到的是整个子组件的实例(包括所有实例上的数据方法等)
                    console.log(this.$refs.kkb);
                    // 调用子组件实例上的hide()即可控制子组件显示隐藏
                    this.$refs.kkb.hide();
                }
            }
        });
    </script>
</body>
</html>

三、$nextTicks

当数据更新的时候,视图并不会立即渲染,这个时候我们期望获取到视图更新后的数据,可以通过 nextTick 来进行操作 。

nextTick 方法将在更新队列循环结束之后立即调用。

需求:点击按钮后,重新渲染一个相同高度的div。此时数据更新后,视图不会立即渲染(每次获取到的是上一次渲染后的结果)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <h1>{{title}}</h1>
        <button @click="setBoxContent">设置新的内容</button>
        <hr>
        <div ref="box" style="background: red" v-html="content"></div>
    </div>
    <script src="./js/vue.js"></script>
    <script>
        
        let app = new Vue({
            el: '#app',
            data: {
                title: '开课吧',
                n: 1
            },
            computed: {
                content() {
                    return new Array(this.n).fill(this.title).join('<br>');
                }
            },
            methods: {
                setBoxContent() {
                    this.n++;
                    // 如果不加$nextTick,则每次获取的都是上一次渲染后的结果
                    // 这里的_表示没有参数时的占位符,用() 或者任意参数e,attr等都行
                    this.$nextTick(_=>{
                        console.log( this.$refs.box.clientHeight );
                    })
                }
            }
        });
    </script>
</body>
</html>

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值