Vue 0基础学习路线-对比React(9)—— 图解深度详述Vue生命周期及创建阶段、挂载阶段和更新阶段及错误捕获、卸载阶段、ref、nextTick详细案例(附详细案例代码解析过程及版本迭代过程)

1. 重点提炼

  • 组件生命周期

    • 创建
    • 挂载
    • 更新
    • 销毁
    • 错误处理
  • ref与$refs

  • nextTick

2. 引例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
    <me-component :t="title"></me-component>
</div>

<script src="./js/vue.js"></script>
<script>

    const meComponent = {
        props: ['t'],
        template: `
                <div>
                    <h1>meComponent - {{t}}</h1>
                </div>
            `,

        beforeCreate() {
            console.log('meComponent:beforeCreate');
        },

        // 构建完成以后
        created() {
            console.log('meComponent:created');
        },

        // 组件挂载之前
        beforeMount() {
            console.log('meComponent:beforeMount');
        },

        // 组件挂载完成以后
        mounted() {
            console.log('meComponent:mounted');
        },

        // 组件更新
        beforeUpdate() {
            console.log('meComponent:beforeUpdate');
        },

        // 组件更新后
        updated() {
            console.log('meComponent:updated');
        },

        beforeDestroy() {
            console.log('meComponent:beforeDestroy');
        },

        destroyed() {
            console.log('meComponent:destroyed');
        }
    };

    let app = new Vue({
        el: '#app',
        data: {
            title: 'Github',
            show: true
        },

        components: {
            meComponent
        },

        beforeCreate() {
            console.log('beforeCreate');
        },

        // 构建完成以后
        created() {
            console.log('created');
        },

        // 组件挂载之前
        beforeMount() {
            console.log('beforeMount');
        },

        // 组件挂载完成以后
        mounted() {
            console.log('mounted');
        },

        // 组件更新
        beforeUpdate() {
            console.log('beforeUpdate');
        },

        // 组件更新后
        updated() {
            console.log('updated');
        },

        beforeDestroy() {
            console.log('beforeDestroy');
        },

        destroyed() {
            console.log('destroyed');
        }
    });

</script>

</body>
</html>

过程有点像递归,即先进后出

父组件从创建阶段到挂载之前阶段的beforeMount(),父组件开始挂载之后,开始解析子组件,解析过程中发现有子组件,再由子组件从创建阶段到挂载阶段完成,最后到父组件挂载阶段的mounted()完成。

最先开始创建的是最外层的组件,但是最先创建完的是最内层的组件。

其实整个就是一个递归解析的过程。

在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.21
Branch: branch04

commit description:a1.21(组件生命周期引例)

tag:a1.21

3. 组件生命周期全流程

new Vue()之后进行初始化事件和生命周期,初始化完成之后再调用beforeCreate(),调用之后在这一步对数据进行依赖注入、劫持、监听等。

完成之后,再调用created()

首先判断是否有el选项,如果有el选项,再判断有没有template选项。

如果有el选项而没有template选项,它会把el选项里内容作为innerhtmlouterhtmltemplate,同时需要手动去挂载,调用$mount(el)

如果有template则解析模板,否则将outerhtml作为模板解析。

把模板解析成虚拟dom以后,这个时候就开始准备挂载了,准备挂载调用beforeMount(),函数执行完成之后对模板进行渲染,然后把渲染后的结果(真实dom)处理完成,最后给$el设置好根组件(根节点)。

这个时候就可以调用组件的根节点$el了。

整个执行完成后,就相当于dom结构树已经渲染完成了,这个时候就执行mounted()函数了。

进入mounted(),这个时候组件已经在页面上了,并时刻监听组件相关的一些变化。

当数据(data)发生改变的时候,会首先执行beforeUpdate(),再对虚拟dom进行补丁式修改(修复)/变更,即根据变化地数据重新组织页面或结构。

然后重新渲染(更新)完成以后,执行updated函数后继续监听组件相关变化。

以后就反复在这里循环了,数据发生变化,走一圈。其实这里就是一个循环,只要数据变化,就会走这个循环。直到将其(组件)从页面(应用中)上移除,当组件移除(或手动调用$destroy方法),就会触发beforeDestroy()

执行beforeDestroy()函数,在这个函数中会对组件进行一些销毁动作,比如把其内部的事件、数据、子组件全部去掉,处理完后,就会触发destroyed()

执行destroyed()函数后,代表全部销毁完成了。

这就是vue组件从无到有的一个过程。

注意:不是所有的生命周期阶段都是可以随意可以使用的,不同的阶段,有不同的事情可以做,具体往下看。

在这里插入图片描述

4. 组件生命周期概述

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

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

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

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <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>
            <me-component v-if="show" :t="title"></me-component>
        </template>
    </div>
    
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

    <script>

        const meComponent = {
            props: ['t'],
            template: `
                <div>
                    <h1>meComponent - {{t.a.b}}</h1>
                </div>
            `,
            beforeCreate() {
                console.log('meComponent:beforeCreate');
                console.log('data', this.$data);
                console.log('el', this.$el);
                console.log('='.repeat(100));
            },
            created() {
                console.log('meComponent:created');
                console.log('data', this.$data);
                console.log('el', this.$el);
                console.log('='.repeat(100));
            },
            beforeMount() {
                console.log('meComponent:beforeMount');
                console.log('data', this.$data);
                console.log('el', this.$el);
                console.log('='.repeat(100));
            },
            mounted() {
                console.log('meComponent:mounted');
                console.log('data', this.$data);
                console.log('el', this.$el);
                console.log('='.repeat(100));
            },
            beforeUpdate() {
                console.log('meComponent:beforeUpdate');
                console.log('props', this.$props);
                console.log('='.repeat(100));
            },
            updated() {
                console.log('meComponent:updated');
                console.log('='.repeat(100));
            },
            beforeDestroy() {
                console.log('meComponent:beforeDestroy');
                console.log('this', this);
                console.log('='.repeat(100));
            },
            destroyed() {
                console.log('meComponent:destroyed');
                console.log('this', this);
                console.log('='.repeat(100));
            }
        }
        
        let app = new Vue({
            el: '#app',
            data: {
                title: 'csdn',
                show: true,
                hasError: false
            },
            components: {
                'me-component': meComponent
            },

            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));
            },
            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>

5. 创建阶段

5.1 beforeCreate()

初始化阶段,应用不多,类似Reactconstructor

我们看最开始的图例,new Vue()之后进行初始化事件和生命周期,初始化完成之后再调用beforeCreate(),调用之后在这一步对数据进行依赖注入、劫持、监听等。完成之后,调用created()

5.2 created()

在实例创建完成后被立即调用,该阶段完成了对 data 中的数据的 observer(对数据的拦截),该阶段可以处理一些异步任务,如发Ajax请求。不过有人也喜欢将异步请求放入beforeMount()mounted()里,其实大致上没多大区别。

看图例,首先判断是否有el选项,如果有el选项,再判断有没有template选项。

如果有el选项而没有template选项,它会把el选项里内容作为innerhtmlouterhtmltemplate,同时需要手动去挂载,调用$mount(el)

如果有template则解析模板,否则将outerhtml作为模板解析。

把模板解析成虚拟dom以后,这个时候就开始准备挂载了,准备挂载调用beforeMount()

6. 挂载阶段

6.1 beforeMount()

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

看图例,由上一步解析成虚拟dom后,调用beforeMount(),函数执行完成之后对模板进行渲染,然后把渲染后的结果(真实dom)处理完成,最后给$el设置好根组件(根节点)。

这个时候就可以调用组件的根节点$el了。

整个执行完成后,就相当于dom结构树已经渲染完成了,这个时候就执行mounted()函数了。

6.2 mounted()

该阶段执行完了模板解析,以及挂载。同时组件根组件元素被赋给了 $el 属性,该阶段可以通过 DOM 操作来对组件内部元素进行处理了。该阶段其实就可以获取 $el 属性值了,即该阶段可进行dom操作了,因为还没解析出来dom,因此在之前的阶段是不能进行dom操作的。

看图例,进入mounted(),这个时候组件已经在页面上了,并时刻监听组件相关的一些变化。看是更新还是销毁等。

7. example02

7.1 example02-1

在根组件中打印$data$el

 beforeCreate() {
                console.log('beforeCreate');
                console.log('data', this.$data);
                console.log('el', this.$el);
            },

都为空,beforeCreate代表组件才开始做事,这个时候数据也没处理,渲染也没完成,因此必然是空的。

如果现在的动作与当前组件的相关数据没有关系的话,就可以在这个生命周期实现了。

在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.22
Branch: branch04

commit description:a1.22(example02-1——beforeCreate组件生命周期——打印data和el)

tag:a1.22

7.2 example02-2

再看看下一阶段 => created

            beforeCreate() {
                console.log('beforeCreate');
                console.log('data', this.$data);
                console.log('el', this.$el);
            },
 
            created() {
                console.log('created');
                console.log('data', this.$data);
                console.log('el', this.$el);
            },

数据已经有了,被拦截了。

但是el还没有,el是在mounted周期才有的。

在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.23
Branch: branch04

commit description:a1.23(example02-2——Created的组件生命周期——打印data和el)

tag:a1.23

7.3 example02-3

 		   beforeMount() {
                console.log('beforeMount');
                console.log('el', this.$el);
            }

这个时候已经可以拿到当前节点了,但是是否真的拿到了吗?

我们换到子组件,看一下。

在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.24
Branch: branch04

commit description:a1.24(example02-3——beforeMount的跟组件生命周期——打印el)

tag:a1.24

7.4 example02-4

子组件beforeMount

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
    <me-component :t="title"></me-component>
</div>

<script src="./js/vue.js"></script>
<script>

    const meComponent = {
        props: ['t'],
        template: `
                <div>
                    <h1>meComponent - {{t}}</h1>
                </div>
            `,

        beforeCreate() {
            console.log('meComponent:beforeCreate');
        },

        // 构建完成以后
        created() {
            console.log('meComponent:created');
        },

        // 组件挂载之前
        beforeMount() {
            console.log('meComponent:beforeMount');
            console.log('el', this.$el);
        },

        // 组件挂载完成以后
        mounted() {
            console.log('meComponent:mounted');
        },

        // 组件更新
        beforeUpdate() {
            console.log('meComponent:beforeUpdate');
        },

        // 组件更新后
        updated() {
            console.log('meComponent:updated');
        },

        beforeDestroy() {
            console.log('meComponent:beforeDestroy');
        },

        destroyed() {
            console.log('meComponent:destroyed');
        }
    };

    let app = new Vue({
        el: '#app',
        data: {
            title: 'Github',
            show: true
        },

        components: {
            meComponent
        },

        beforeCreate() {
            console.log('beforeCreate');
            console.log('data', this.$data);
            console.log('el', this.$el);
        },

        // 构建完成以后
        created() {
            console.log('created');
            console.log('data', this.$data);
            console.log('el', this.$el);
        },

        // 组件挂载之前
        beforeMount() {
            console.log('beforeMount');
            console.log('el', this.$el);
        },

        // 组件挂载完成以后
        mounted() {
            console.log('mounted');
        },

        // 组件更新
        beforeUpdate() {
            console.log('beforeUpdate');
        },

        // 组件更新后
        updated() {
            console.log('updated');
        },

        beforeDestroy() {
            console.log('beforeDestroy');
        },

        destroyed() {
            console.log('destroyed');
        }
    });

</script>

</body>
</html>

我们看显示结果有点不一样了,子组件的elundefined,根组件是有数据的。

因为子组件是写在一个模板中的,模板没解析完成,肯定获取不到。

为啥根组件可以,因为根组件直接取的是页面上已经存在的元素,即<div id="app">

如果根组件模板拿的并不是outerhtml,而是template的话,这样就el就获取不到了。

所以实际上beforeMount的生命周期实际上是拿不到组件的$el,注意此种特殊情况。

在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.25
Branch: branch04

commit description:a1.25(example02-4——beforeMount的子组件生命周期——打印el)

tag:a1.25

7.5 example02-5

根组件 => mounted

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
    <me-component :t="title"></me-component>
</div>

<script src="./js/vue.js"></script>
<script>

    const meComponent = {
        props: ['t'],
        template: `
                <div>
                    <h1>meComponent - {{t}}</h1>
                </div>
            `,

        beforeCreate() {
            console.log('meComponent:beforeCreate');
        },

        // 构建完成以后
        created() {
            console.log('meComponent:created');
        },

        // 组件挂载之前
        beforeMount() {
            console.log('meComponent:beforeMount');
            console.log('el', this.$el);
        },

        // 组件挂载完成以后
        mounted() {
            console.log('meComponent:mounted');
        },

        // 组件更新
        beforeUpdate() {
            console.log('meComponent:beforeUpdate');
        },

        // 组件更新后
        updated() {
            console.log('meComponent:updated');
        },

        beforeDestroy() {
            console.log('meComponent:beforeDestroy');
        },

        destroyed() {
            console.log('meComponent:destroyed');
        }
    };

    let app = new Vue({
        el: '#app',
        data: {
            title: 'Github',
            show: true
        },

        components: {
            meComponent
        },

        beforeCreate() {
            console.log('beforeCreate');
            console.log('data', this.$data);
            console.log('el', this.$el);
        },

        // 构建完成以后
        created() {
            console.log('created');
            console.log('data', this.$data);
            console.log('el', this.$el);
        },

        // 组件挂载之前
        beforeMount() {
            console.log('beforeMount');
            console.log('el', this.$el);
        },

        // 组件挂载完成以后
        mounted() {
            console.log('mounted');
            console.log('el', this.$el);
        },

        // 组件更新
        beforeUpdate() {
            console.log('beforeUpdate');
        },

        // 组件更新后
        updated() {
            console.log('updated');
        },

        beforeDestroy() {
            console.log('beforeDestroy');
        },

        destroyed() {
            console.log('destroyed');
        }
    });

</script>

</body>
</html>

根组件mounted()生命周期才是实际能取到el的生命周期,组件已经挂载完成了。

在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.26
Branch: branch04

commit description:a1.26(example02-5——Mounted的根组件生命周期——打印el)

tag:a1.26

以上其实就是组件从无到有的一个过程。

8. 更新阶段

8.1 beforeUpdate()

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

看图例,执行完毕mounted() => 刻监听组件相关的一些变化

当数据(data)发生改变的时候,会首先执行beforeUpdate(),再对虚拟dom进行补丁式修改(修复)/变更,即根据变化地数据重新组织页面或结构。

然后重新渲染(更新)完成以后,执行updated函数。

8.2 updated()

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

看图例,重新渲染(更新)完成以后,执行updated函数后继续监听组件相关变化。

以后就反复在这里循环了,数据发生变化,走一圈。其实这里就是一个循环,只要数据变化,就会走这个循环。直到将其(组件)从页面(应用中)上移除,当组件移除(或手动调用$destroy方法),就会触发beforeDestroy()

9. example03

9.1 example03-1

隐藏子组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
    <button @click="show=!show">隐藏</button>
    <me-component :t="title" v-if="show"></me-component>
</div>

<script src="./js/vue.js"></script>
<script>

    const meComponent = {
        props: ['t'],
        template: `
                <div>
                    <h1>meComponent - {{t}}</h1>
                </div>
            `,

        beforeCreate() {
            console.log('meComponent:beforeCreate');
        },

        // 构建完成以后
        created() {
            console.log('meComponent:created');
        },

        // 组件挂载之前
        beforeMount() {
            console.log('meComponent:beforeMount');
            console.log('el', this.$el);
        },

        // 组件挂载完成以后
        mounted() {
            console.log('meComponent:mounted');
        },

        // 组件更新
        beforeUpdate() {
            console.log('meComponent:beforeUpdate');
        },

        // 组件更新后
        updated() {
            console.log('meComponent:updated');
        },

        beforeDestroy() {
            console.log('meComponent:beforeDestroy');
        },

        destroyed() {
            console.log('meComponent:destroyed');
        }
    };

    let app = new Vue({
        el: '#app',
        data: {
            title: 'Github',
            show: true
        },

        components: {
            meComponent
        },

        beforeCreate() {
            console.log('beforeCreate');
            console.log('data', this.$data);
            console.log('el', this.$el);
        },

        // 构建完成以后
        created() {
            console.log('created');
            console.log('data', this.$data);
            console.log('el', this.$el);
        },

        // 组件挂载之前
        beforeMount() {
            console.log('beforeMount');
            console.log('el', this.$el);
        },

        // 组件挂载完成以后
        mounted() {
            console.log('mounted');
            console.log('el', this.$el);
        },

        // 组件更新
        beforeUpdate() {
            console.log('beforeUpdate');
        },

        // 组件更新后
        updated() {
            console.log('updated');
        },

        beforeDestroy() {
            console.log('beforeDestroy');
        },

        destroyed() {
            console.log('destroyed');
        }
    });

</script>

</body>
</html>

点击隐藏按钮 =>

这个时候会触发根组件beforeUpdate、updated生命周期,

以及子组件的meComponent:beforeDestroy、meComponent:destroyed生命周期。

因为用的v-if,此时就会把子组件从页面中删除,因此触发了销毁。

在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.27
Branch: branch04

commit description:a1.27(example03-1——v-if隐藏子组件)

tag:a1.27

9.2 example03-2

点击按钮 => 更新数据 => title

  <div id="app">
        <button @click="show=!show">隐藏</button>
        <button @click="title='me'">更新title</button>
        <me-component :t="title"></me-component>
    </div>

更新title后,此时触发父组件的beforeUpdate、子组件的meComponent:beforeUpdate、meComponent:updated,及父组件的updated

触发父组件更新后 => 子组件也会更新。

在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.28
Branch: branch04

commit description:a1.28(example03-2——点击按钮 => 更新数据 => title)

tag:a1.28

9.3 example03-3

页面放两个需要可以更新的子组件。

   <div id="app">
        <button @click="show=!show">隐藏</button>
        <button @click="title='me'">更新title</button>
        <me-component :t="title"></me-component>
        <me-component :t="title"></me-component>
    </div>

点击更新title=> 此时子组件的生命周期触发了两次,因为这两个子组件都需要更新。

在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.29
Branch: branch04

commit description:a1.29(example03-3——点击按钮 => 更新数据(两个子组件) => title)

tag:a1.29

9.4 example03-4

设置两个不同title的子组件,点击按钮 => 只更新:t="title"

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
    <button @click="show=!show">隐藏</button>
    <button @click="title='me'">更新title</button>
    <me-component :t="title"></me-component>
    <me-component :t="subtitle"></me-component>
</div>

<script src="./js/vue.js"></script>
<script>

    const meComponent = {
        props: ['t'],
        template: `
                <div>
                    <h1>meComponent - {{t}}</h1>
                </div>
            `,

        beforeCreate() {
            console.log('meComponent:beforeCreate');
        },

        // 构建完成以后
        created() {
            console.log('meComponent:created');
        },

        // 组件挂载之前
        beforeMount() {
            console.log('meComponent:beforeMount');
            console.log('el', this.$el);
        },

        // 组件挂载完成以后
        mounted() {
            console.log('meComponent:mounted');
        },

        // 组件更新
        beforeUpdate() {
            console.log('meComponent:beforeUpdate');
        },

        // 组件更新后
        updated() {
            console.log('meComponent:updated');
        },

        beforeDestroy() {
            console.log('meComponent:beforeDestroy');
        },

        destroyed() {
            console.log('meComponent:destroyed');
        }
    };

    let app = new Vue({
        el: '#app',
        data: {
            title: 'Github',
            subtitle: '前端开发',   // 子标题
            show: true
        },

        components: {
            meComponent
        },

        beforeCreate() {
            console.log('beforeCreate');
            console.log('data', this.$data);
            console.log('el', this.$el);
        },

        // 构建完成以后
        created() {
            console.log('created');
            console.log('data', this.$data);
            console.log('el', this.$el);
        },

        // 组件挂载之前
        beforeMount() {
            console.log('beforeMount');
            console.log('el', this.$el);
        },

        // 组件挂载完成以后
        mounted() {
            console.log('mounted');
            console.log('el', this.$el);
        },

        // 组件更新
        beforeUpdate() {
            console.log('beforeUpdate');
        },

        // 组件更新后
        updated() {
            console.log('updated');
        },

        beforeDestroy() {
            console.log('beforeDestroy');
        },

        destroyed() {
            console.log('destroyed');
        }
    });

</script>

</body>
</html>

点击按钮 =>

这个时候,点击发现只有一个子组件更新了。

在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.30
Branch: branch04

commit description:a1.30(example03-4——设置两个不同title的子组件,点击按钮 => 只更新:t="title"

tag:a1.30

9.4.1 深度探究

当一个组件更新过程中,其内部数据更新的时候,这个组件就会重新渲染,但是重新渲染,并不代表整个内容全部销毁重来,而是只会改变得东西。不变的东西,是不会发生任何变化的。 => 组件复用

当一个组件props发生改变的时候,就会触发该组件的重新渲染。

一个组件的dataprops不论谁更新了,都会触发该组件的重新渲染。

但是如果某个组件(如本例 => <me-component :t="subtitle"></me-component>)在渲染的过程中,它没有更新数据,这个组件就不会调用重新渲染的接口。

9.4.2 与React差异精细对比

以上与React似乎不太一样!

React中更新以后,其组件是都会更新的,即父组件数据更新,子组件都会触发对应的生命周期,在其生命周期中决定是否渲染,生命周期在React当中是一定会触发的,但是会不会渲染重新构建这个结构是不一定的,它会有自己的diff算法去比较。

而在Vue当中是不一样的,前期只要发现没更新,连生命周期函数都不执行的!

因此VueReact大体思想一样,只是有一些细微的差别。

9.4.2.1 精记

React=> 更新了都会去执行生命周期,但是需不需要渲染整个结构,看比较的结果。

Vue => 发现了数据不更新就压根不管,根本不会进入生命周期。

10. 错误捕获

10.1 errorCaptured()

当捕获一个来自子孙组件的错误时被调用,此钩子会收到三个参数:

错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。

此钩子可以返回 false 以阻止该错误继续向上传播。

10.1.1 example04

10.1.1.1 example04-1
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
    <h1>{{title}}</h1>
    <hr>
    <template v-if="hasError">
        <h4>有错误发生了</h4>
    </template>
    <template v-else>
        <me-component v-if="show" :t="title"></me-component>
    </template>
</div>

<script src="./js/vue.js"></script>
<script>

    const meComponent = {
        props: ['t'],
        template: `
            <div>
                <h1>meComponent - {{t}}</h1>
            </div>
        `,

        beforeCreate() {
            console.log('meComponent:beforeCreate');
        },

        // 构建完成以后
        created() {
            console.log('meComponent:created');
        },

        // 组件挂载之前
        beforeMount() {
            console.log('meComponent:beforeMount');
            console.log('el', this.$el);
        },

        // 组件挂载完成以后
        mounted() {
            console.log('meComponent:mounted');
        },

        // 组件更新
        beforeUpdate() {
            console.log('meComponent:beforeUpdate');
        },

        // 组件更新后
        updated() {
            console.log('meComponent:updated');
        },

        beforeDestroy() {
            console.log('meComponent:beforeDestroy');
        },

        destroyed() {
            console.log('meComponent:destroyed');
        }
    };

    let app = new Vue({
        el: '#app',
        data: {
            title: 'Github',
            show: true,
            hasError: false
        },

        components: {
            meComponent
        },

        beforeCreate() {
            console.log('beforeCreate');
            console.log('data', this.$data);
            console.log('el', this.$el);
        },

        // 构建完成以后
        created() {
            console.log('created');
            console.log('data', this.$data);
            console.log('el', this.$el);
        },

        // 组件挂载之前
        beforeMount() {
            console.log('beforeMount');
            console.log('el', this.$el);
        },

        // 组件挂载完成以后
        mounted() {
            console.log('mounted');
            console.log('el', this.$el);
        },

        // 组件更新
        beforeUpdate() {
            console.log('beforeUpdate');
        },

        // 组件更新后
        updated() {
            console.log('updated');
        },

        beforeDestroy() {
            console.log('beforeDestroy');
        },

        destroyed() {
            console.log('destroyed');
        },

        //err:错误  vm:虚拟dom(组件实例)  info:信息
        errorCaptured(err, vm, info) {
            console.log('errorCaptured');
            console.log(err, vm, info);
            this.hasError = true;
            return false;
        }
    });

</script>

</body>
</html>

此时正常显示,子组件没有错误。

在这里插入图片描述

发生错误: => 调用不存在的数据

const meComponent = {
            props: ['t'],
            template: `
                <div>
                    <h1>meComponent - {{t.a.b}}</h1>
                </div>
            `,

错误被浏览器拦截捕获了。

在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.31
Branch: branch04

commit description:a1.31(example04-1——错误捕获使用)

tag:a1.31

10.1.1.2 example04-2

不进行错误显示。

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

错误被浏览器拦截捕获了。

在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.32
Branch: branch04

commit description:a1.32(example04-2——错误捕获使用—页面不做处理)

tag:a1.32

10.1.1.3 example04-3

不做捕获处理。

        //err:错误  vm:虚拟dom(组件实例)  info:信息
        // errorCaptured(err, vm, info) {
        //     console.log('errorCaptured');
        //     console.log(err, vm, info);
        //     this.hasError = true;
        //     return false;
        // }

如果错误也不处理了,直接交给vue。 => 浏览器直接抛出error

报错,这样显示给用户没有任何效果,这样显示不太友好,所以最好父级拦截错误,并根据拦截到的错误,给用户展示比较好的页面(如给用户展示 => 页面出现故障,请联系管理,附上联系方式),不要让用户感觉应用做的很差。

React的错误生命周期的概念是一样的,用法、作用也是一样的。

在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.33
Branch: branch04

commit description:a1.33(example04-3——错误捕获使用—页面不做处理、不做捕获处理)

tag:a1.33

11. 卸载阶段

11.1 beforeDestroy()

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

看图例,执行beforeDestroy()函数,在这个函数中会对组件进行一些销毁动作,比如把其内部的事件、数据、子组件全部去掉,处理完后,就会触发destroyed()

11.2 destroyed()

Vue 实例销毁后调用

看图例,执行destroyed()函数,代表全部销毁完成了。

12. example05

12.1 example05-1

组件销毁的时候,会把组件的事件绑定等等也会处理掉,但是有些东西是处理不掉的。

即与它的数据没有绑定的东西,比如定时器。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">

    <button @click="show=!show">隐藏</button>
    <me-component v-if="show" :t="title"></me-component>

</div>

<script src="./js/vue.js"></script>
<script>

    const meComponent = {
        props: ['t'],
        template: `
            <div>
                <h1>meComponent - {{t}}</h1>
            </div>
        `,

        created() {
            setInterval(() => {
                console.log(1);
            }, 100);
        },

        beforeDestroy() {
            console.log('meComponent:beforeDestroy');
        },

        destroyed() {
            console.log('meComponent:destroyed');
        }
    };

    let app = new Vue({
        el: '#app',
        data: {
            title: 'Github',
            show: true,
        },
        components: {
            meComponent
        }
    });

</script>

</body>
</html>

虽然销毁了组件,会发现定时器还是存在的!

这是一个大坑,因为销毁组件的生命周期不会自动帮你调用clear

类似这种东西,得手动自行销毁。如在组件生命周期中发了一个请求,但是这个请求比较耗时,但是还没请求完毕,组件就销毁了。

但是这个请求也不会随着组件的销毁而取消,所以需要手动回收和处理请求。

在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.34
Branch: branch04

commit description:a1.34(example05-1——卸载阶段-处理不了异步相关内容)

tag:a1.34

12.2 example05-2

所以很多可以长时间存在,比较耗时的任务,又与组件本身没有太大的关系,这个时候得需要我们自己手动清理。

    const meComponent = {
        props: ['t'],
        template: `
            <div>
                <h1>meComponent - {{t}}</h1>
            </div>
        `,

        created() {
            this._timer = setInterval(() => {
                console.log(1);
            }, 100);
        },

        beforeDestroy() {
            console.log('meComponent:beforeDestroy');
        },

        destroyed() {
            console.log('meComponent:destroyed');
            clearInterval(this._timer);
        }
    };

    let app = new Vue({
        el: '#app',
        data: {
            title: 'Github',
            show: true,
        },
        components: {
            meComponent
        }
    });

这个时候就会发现定时器也没了。

在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.35
Branch: branch04

commit description:a1.35(example05-2——卸载阶段-处理不了异步相关内容 => 自行销毁)

tag:a1.35

13. ref 与 $refs

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>

    <div id="app">
        <h1>{{title}}</h1>
        <button @click="getBoxHeight">获取 box 的高度</button>
        <button @click="getmeComponent">获取自定义组件实例及内部方法</button>
        <hr>
        <div ref="box">
            这是内容<br>这是内容<br>这是内容<br>这是内容<br>这是内容<br>
        </div>
        <hr>
        <me-component ref="me" :t="title"></me-component>
    </div>
    
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

    <script>

        const meComponent = {
            props: ['t'],
            data() {
                return {
                    isShow: true
                }
            },
            template: `
                <div v-if="isShow">
                    <h1>meComponent - {{t}}</h1>
                </div>
            `,
            methods: {
                hide() {
                    this.isShow = false;
                }
            }
        }
        
        let app = new Vue({
            el: '#app',
            data: {
                title: 'csdn'
            },
            components: {
                'me-component': meComponent
            },
            mounted() {
                console.log(this.$refs.me);
            },
            methods: {
                getBoxHeight() {
                    console.log( this.$refs.box.clientHeight );
                },
                getmeComponent() {
                    this.$refs.me.hide();
                }
            }
        });
    </script>
</body>
</html>

在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.36
Branch: branch04

commit description:a1.36(ref 与 $refs)

tag:a1.36

13.1 ref

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

13.2 $refs

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

14. nextTick

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <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="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

    <script>
        
        let app = new Vue({
            el: '#app',
            data: {
                title: 'csdn',
                n: 1
            },
            computed: {
                content() {
                    return new Array(this.n).fill(this.title).join('<br>');
                }
            },
            methods: {
                setBoxContent() {
                    this.n++;
                    this.$nextTick(_=>{
                        console.log( this.$refs.box.clientHeight );
                    })
                }
            }
        });
    </script>
</body>
</html>

nextTick 方法将在更新队列循环结束之后立即调用
在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.37
Branch: branch04

commit description:a1.37(nextTick使用)

tag:a1.37



(后续待补充)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值