一、组件生命周期
图示解析:
此图为vue官网提供的vue生命周期的整个过程:
- 组件最开始从new Vue()开始组件生成对象开始,当然也可以是从Vue.component()构建组件对象开始,两者没有多大区别。在 new Vue()时就相当于在执行构造函数,在构造函数中,即从组件创建到组件最后出现,并可以使用经历了很多过程;
- init Events & Lifecycel:初始化一些数据、事件、生命周期等,可以认为是属性初始化或工具初始化;
- 完成以后就会去执行beforeCreate函数;
- init injectiongs&reactivity :beforeCreate函数调用完成后,就会再去初始化注入依赖、数据拦截等;
- 完成后就会去执行created函数,即vue组件构建完成;以后所以环节已经将vue组件的所有核心内容准备好;
- 判断当前是否有el选项,有则继续判断是否有template选项;无则通过$mount(el)手动渲染组件;
- el和template都准备好以后,就会进去组件编译阶段(compile template into render function),对组件模块进行编译加载;
- 整个组件编译完后,产生了整个DOM节点后,会调用beforeMount()方法,
- 调用beforeMount()方法就会进行挂载,挂载到父级el选项中,并给vue的组件实例增加el选项;
- 整个挂载完成后,调用mounted(),进行组件的渲染。
- 到此为止从组件的首次初始化,首次渲染,再到首次挂载到页面中的整个过程,执行完成
- 最后进入一个循环阶段,称为更新阶段;组件挂载完成后并不是不做任何事了,而是不断去监听组件的各种数据和状态变化、更新
- 当数据发生改变时,就会触发beforeUpdate(),执行完成就,组件就会对DOM进行重新渲染(Virtual DOM re-render and patch)
- 渲染完成后执行update,重新更新组件
- 后续整个工作过程都是不断执行更新组件这个过程,直到进行组件销毁,此时就会触发beforeDestroy();
- 销毁时触发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示例
需求:
- 子组件自己点击隐藏和显示;
- 根组件,点击按钮获取整个div高度(加ref属性,通过$refs获取);
- 根组件控制子组件的隐藏和显示(根组件中获取子组件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.kkbconsole.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>