此学习教程是对官方教程的解析,
本章节主要涉及到的官方教程地址:
上一章 :vue生命周期实战(二)——实例挂载
本章节介绍实例更新阶段的生命周期钩子:beforeUpdate和updated
beforeUpdate、updated和beforeMount、mounted的区别
beforeUpdate、updated和beforeMount、mounted主要区别在于虚拟dom是初次渲染还是重新渲染。
生命周期钩子 | 区别 |
---|---|
beforeMount | 虚拟dom以及非异步子组件初次渲染并挂载前触发 因为在初次渲染前, 所以在这个钩子中非异步更改响应式属性,不会重新渲染,从而不会触发beforeUpdate和updated钩子 |
mounted | 虚拟dom以及非异步子组件都已经初次渲染并挂载后触发 因为已初次渲染, 所以在这个钩子中更改响应式属性,将重新渲染,会触发beforeUpdate和updated钩子 |
beforeUpdate | 以下三种情况导致的虚拟dom重新渲染前触发: 1.初次渲染前(mounted之前生命周期钩子)异步更改响应式属性 2.初次渲染后更改响应式属性 3.异步子组件初次渲染并挂载前 在这个钩子中避免更改响应式属性。因为若赋值给响应式属性不是一个固定的值而是一个不断动态变化的值,比如像这样this.a++,则此更改将会导致重新渲染触发自身(beforeUpdate钩子),beforeUpdate钩子又会再次执行更改,又重新导致渲染触发自身(beforeUpdate钩子),如此循环往复,造成死循环 |
updated | 以下三种情况导致的虚拟dom重新渲染后触发: 1.初次渲染前(mounted之前生命周期钩子)异步更改响应式属性 2.初次渲染后更改响应式属性 3.异步子组件初次渲染并挂载后 在这个钩子中避免更改响应式属性。因为若赋值给响应式属性不是一个固定的值而是一个不断动态变化的值,比如像这样this.a++,则此更改将会导致重新渲染触发自身(updated钩子),updated钩子又会再次执行更改,又重新导致渲染触发自身(updated钩子),如此循环往复,造成死循环 |
beforeUpdate和updated的区别
beforeUpdate和updated的具体区别如下:
生命周期钩子 | 可获取的对象 | 常见应用场景 |
---|---|---|
beforeUpdate | 和mounted可获取对象相同 | 适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器 |
updated | 和mounted可获取对象相同 | – |
综合例子
单个组件例子
不更改响应式属性
<!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>vue生命周期学习</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
</head>
<body>
<div id="app">
<ul ref="node">
<li v-for="(item,index) in maxLen" :key="index">{{item}}</li>
</ul>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
maxLen: 3
},
beforeCreate() {
console.log('beforeCreate',document.querySelectorAll('li').length)
},
created() {
console.log('created',document.querySelectorAll('li').length)
},
beforeMount() {
console.log('beforeMount',document.querySelectorAll('li').length)
},
mounted() {
console.log('mounted',document.querySelectorAll('li').length)
},
beforeUpdate() {
console.log('beforeUpdate',document.querySelectorAll('li').length)
},
updated(){
console.log('updated',document.querySelectorAll('li').length)
}
})
</script>
</body>
</html>
运行结果:
可以看到,仅仅是new一个Vue实例的话,只输出before、created、beforeMount、mounted四个生命周期钩子。而且beforeCreate、created、beforeMount显示<li>
元素数量只有1,而mounted显示4,说明beforeCreate、created、beforeMount三个钩子在初次渲染前,而mounted钩子在初次渲染后。
初次渲染前非异步更改响应式属性
<!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>vue生命周期学习</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
</head>
<body>
<div id="app">
<ul ref="node">
<li v-for="(item,index) in maxLen" :key="index">{{item}}</li>
</ul>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
maxLen: 3
},
beforeCreate() {
this.maxLen = 4;
console.log('beforeCreate',document.querySelectorAll('li').length)
},
created() {
this.maxLen = 5;
console.log('created',document.querySelectorAll('li').length)
},
beforeMount() {
this.maxLen = 6;
console.log('beforeMount',document.querySelectorAll('li').length)
},
mounted() {
console.log('mounted',document.querySelectorAll('li').length)
},
beforeUpdate() {
console.log('beforeUpdate',document.querySelectorAll('li').length)
},
updated(){
console.log('updated',document.querySelectorAll('li').length)
}
})
</script>
</body>
</html>
运行结果:
可以看到,初次渲染前更改响应式属性的话,还是只输出before、created、beforeMount、mounted四个生命周期钩子。而且before、created、beforeMount都对同一个响应式属性更改的话,mounted钩子只执行一次,那是因为是顺序执行的,所以以最后一个钩子即beforeMount为准。所以在mounted可以看到,最后渲染的<li>
元素数量为6。
初次渲染后更改响应式属性
<!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>vue生命周期学习</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
</head>
<body>
<div id="app">
<ul ref="node">
<li v-for="(item,index) in maxLen" :key="index">{{item}}</li>
</ul>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
maxLen: 3
},
beforeCreate() {
this.maxLen = 4;
console.log('beforeCreate',document.querySelectorAll('li').length)
},
created() {
this.maxLen = 5;
console.log('created',document.querySelectorAll('li').length)
},
beforeMount() {
this.maxLen = 6;
console.log('beforeMount',document.querySelectorAll('li').length)
},
mounted() {
this.maxLen = 7;
console.log('mounted',document.querySelectorAll('li').length)
},
beforeUpdate() {
console.log('beforeUpdate',document.querySelectorAll('li').length)
},
updated(){
console.log('updated',document.querySelectorAll('li').length)
}
})
</script>
</body>
</html>
运行结果:
可以看到,初次渲染后(此处是在mounted钩子中)更改响应式属性, 会触发beforeUpdate、updated钩子。而且mounted、beforeUpdate显示<li>
元素数量为6,而updated显示7,说明mounted、beforeUpdate钩子在重新渲染前,而updated钩子在重新渲染后。
注意, 在beforeCreate钩子里面非异步方式更改响应式属性this.maxLen = 4;
是无效的,因为此时响应式属性还未注入到Vue实例中。
使用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>vue生命周期学习</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
</head>
<body>
<div id="app">
<ul ref="node">
<li v-for="(item,index) in maxLen" :key="index">{{item}}</li>
</ul>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
maxLen: 3
},
beforeCreate() {
this.maxLen = 4;
this.$nextTick(function () {
console.log('beforeCreate nextTick',document.querySelectorAll('li').length)
})
console.log('beforeCreate',document.querySelectorAll('li').length)
},
created() {
this.maxLen = 5;
this.$nextTick(function () {
console.log('created nextTick',document.querySelectorAll('li').length)
})
console.log('created',document.querySelectorAll('li').length)
},
beforeMount() {
this.maxLen = 6;
this.$nextTick(function () {
console.log('beforeMount nextTick',document.querySelectorAll('li').length)
})
console.log('beforeMount',document.querySelectorAll('li').length)
},
mounted() {
this.maxLen = 7;
this.$nextTick(function () {
console.log('mounted nextTick',document.querySelectorAll('li').length)
})
console.log('mounted',document.querySelectorAll('li').length)
},
beforeUpdate() {
console.log('beforeUpdate',document.querySelectorAll('li').length)
},
updated(){
console.log('updated',document.querySelectorAll('li').length)
}
})
</script>
</body>
</html>
运行结果:
可以看到,beforeCreate nextTick、created nextTick、beforeMount nextTick运行在mounted之后,显示的结果为6,这说明这三个钩子中的nextTick在初次渲染后重新渲染前执行。
mounted nextTick运行在beforeUpdate、updated之后,显示的结果为7, 说明mounted的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>vue生命周期学习</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
</head>
<body>
<div id="app">
<ul ref="node">
<li v-for="(item,index) in maxLen" :key="index">{{item}}</li>
</ul>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
maxLen: 3
},
beforeCreate() {
vm = this;
setTimeout(function () {
vm.maxLen = 4;
})
console.log('beforeCreate',document.querySelectorAll('li').length)
},
created() {
vm = this;
setTimeout(function () {
vm.maxLen = 5;
})
console.log('created',document.querySelectorAll('li').length)
},
beforeMount() {
vm = this;
setTimeout(function () {
vm.maxLen = 6;
})
console.log('beforeMount',document.querySelectorAll('li').length)
},
mounted() {
vm = this;
setTimeout(function () {
vm.maxLen = 7;
})
console.log('mounted',document.querySelectorAll('li').length)
},
beforeUpdate() {
console.log('beforeUpdate',document.querySelectorAll('li').length)
},
updated(){
console.log('updated',document.querySelectorAll('li').length)
}
})
</script>
</body>
</html>
可以看到每次异步更改响应式属性,都会重新渲染,触发beforeUpate、updated钩子。而是排在mounted后面。那是因为beforeCreate、created、beforeMount、mounted钩子是同步任务,优先顺序执行; 而setTimout是异步执行的,放在一个异步队列里面,只有同步任务执行完了,才执行异步任务。
使用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>vue生命周期学习</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
</head>
<body>
<div id="app">
<ul ref="node">
<li v-for="(item,index) in maxLen" :key="index">{{item}}</li>
</ul>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
maxLen: 3
},
beforeCreate() {
vm = this;
setTimeout(function () {
vm.maxLen = 4;
vm.$nextTick(function () {
console.log('beforeCreate nextTick',document.querySelectorAll('li').length)
})
})
console.log('beforeCreate',document.querySelectorAll('li').length)
},
created() {
vm = this;
setTimeout(function () {
vm.maxLen = 5;
vm.$nextTick(function () {
console.log('created nextTick',document.querySelectorAll('li').length)
})
})
console.log('created',document.querySelectorAll('li').length)
},
beforeMount() {
vm = this;
setTimeout(function () {
vm.maxLen = 6;
vm.$nextTick(function () {
console.log('beforeMount nextTick',document.querySelectorAll('li').length)
})
})
console.log('beforeMount',document.querySelectorAll('li').length)
},
mounted() {
vm = this;
setTimeout(function () {
vm.maxLen = 7;
vm.$nextTick(function () {
console.log('mounted nextTick',document.querySelectorAll('li').length)
})
})
console.log('mounted',document.querySelectorAll('li').length)
},
beforeUpdate() {
console.log('beforeUpdate',document.querySelectorAll('li').length)
},
updated(){
console.log('updated',document.querySelectorAll('li').length)
}
})
</script>
</body>
</html>
运行结果:
可以看到nextTick在每个updated后执行,所以它总能正确返回每次渲染的结果。
注意, 在beforeCreate钩子里面异步方式更改响应式属性vm.maxLen = 4;
是有效的,因为异步任务在mounted之后执行,此时更改响应式属性会触发重新渲染。
父子组件例子
让我们将上面的例子改造下,变成父子组件。
父组件(其实是父实例,不过组件与实例本质是一样的):vm
非异步子组件:v-ul
<!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>vue生命周期学习</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<v-ul :max="maxLen" :style="{color:ulcolor}">
</v-ul>
</div>
<script>
Vue.component('v-ul', {
template: '\
<ul :style="{fontSize:size}">\
<li v-for="(item,index) in max" :key="index">{{item}}</li>\
</ul>\
',
props: ['max'],
data: function(){
return {"size":"18px"}
},
beforeCreate() {
console.log('ul beforeCreate',document.querySelectorAll('li').length)
},
created() {
console.log('ul created',document.querySelectorAll('li').length)
},
beforeMount() {
console.log('ul beforeMount',document.querySelectorAll('li').length)
},
mounted() {
console.log('ul mounted',document.querySelectorAll('li').length)
},
beforeUpdate() {
console.log('ul beforeUpdate',document.querySelectorAll('li').length)
},
updated(){
console.log('ul beforeUpdate',document.querySelectorAll('li').length)
}
})
var vm = new Vue({
el: '#app',
data: {
maxLen: 3,
ulcolor: "dark"
},
beforeCreate() {
console.log('vm beforeCreate',document.querySelectorAll('li').length)
},
created() {
console.log('vm created',document.querySelectorAll('li').length)
},
beforeMount() {
console.log('vm beforeMount',document.querySelectorAll('li').length)
},
mounted() {
console.log('vm mounted',document.querySelectorAll('li').length)
},
beforeUpdate() {
console.log('vm beforeUpdate',document.querySelectorAll('li').length)
},
updated(){
console.log('vm updated',document.querySelectorAll('li').length)
}
})
</script>
</body>
</html>
运行结果:
可以看到, 在父组件vm的beforeMount和mounted钩子中间,又执行了非异步子组件的beforeCreate、created、beforeMount、mounted钩子。所以对于父组件来说,在mounted钩子中,非异步子组件是必然挂载好的。
父组件更改响应式属性
父组件更改与子组件props不绑定的响应式属性
在初步渲染前(created或beforeMount中)非异步更改父组件的ulcolor属性并不会触发重新渲染;
在异步更改或在初步渲染后(比如mounted中)非异步更改父组件的ulcolor属性会触发重新渲染:
<!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>vue生命周期学习</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<v-ul :max="maxLen" :style="{color:ulcolor}">
</v-ul>
</div>
<script>
Vue.component('v-ul', {
template: '\
<ul :style="{fontSize:size}">\
<li v-for="(item,index) in max" :key="index">{{item}}</li>\
</ul>\
',
props: ['max'],
data: function(){
return {"size":"18px"}
},
beforeCreate() {
console.log('ul beforeCreate',document.querySelectorAll('li').length)
},
created() {
//异步更改响应式属性
/*
var vm = this;
setTimeout(function () {
vm.ulcolor = "red"
})
*/
console.log('ul created',document.querySelectorAll('li').length)
},
beforeMount() {
console.log('ul beforeMount',document.querySelectorAll('li').length)
},
mounted() {
console.log('ul mounted',document.querySelectorAll('li').length)
},
beforeUpdate() {
console.log('ul beforeUpdate',document.querySelectorAll('li').length)
},
updated(){
console.log('ul beforeUpdate',document.querySelectorAll('li').length)
}
})
var vm = new Vue({
el: '#app',
data: {
maxLen: 3,
ulcolor: "dark"
},
beforeCreate() {
console.log('vm beforeCreate',document.querySelectorAll('li').length)
},
created() {
console.log('vm created',document.querySelectorAll('li').length)
},
beforeMount() {
console.log('vm beforeMount',document.querySelectorAll('li').length)
},
mounted() {
this.ulcolor = "red"
console.log('vm mounted',document.querySelectorAll('li').length)
},
beforeUpdate() {
console.log('vm beforeUpdate',document.querySelectorAll('li').length)
},
updated(){
console.log('vm updated',document.querySelectorAll('li').length)
}
})
</script>
</body>
</html>
可以看到,在初次渲染之后更改和子组件不关联的响应式属性时,只会执行父组件的beforeUpdate、updated钩子。
父组件更改与子组件props绑定的响应式属性
在初步渲染前(created或beforeMount中)非异步更改父组件的maxLen属性并不会触发重新渲染;
在异步更改或在初步渲染后(比如mounted中)非异步更改父组件的与子组件关联的maxLen属性:
<!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>vue生命周期学习</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<v-ul :max="maxLen" :style="{color:ulcolor}">
</v-ul>
</div>
<script>
Vue.component('v-ul', {
template: '\
<ul :style="{fontSize:size}">\
<li v-for="(item,index) in max" :key="index">{{item}}</li>\
</ul>\
',
props: ['max'],
data: function(){
return {"size":"18px"}
},
beforeCreate() {
console.log('ul beforeCreate',document.querySelectorAll('li').length)
},
created() {
console.log('ul created',document.querySelectorAll('li').length)
},
beforeMount() {
console.log('ul beforeMount',document.querySelectorAll('li').length)
},
mounted() {
console.log('ul mounted',document.querySelectorAll('li').length)
},
beforeUpdate() {
console.log('ul beforeUpdate',document.querySelectorAll('li').length)
},
updated(){
console.log('ul updated',document.querySelectorAll('li').length)
}
})
var vm = new Vue({
el: '#app',
data: {
maxLen: 3,
ulcolor: "dark"
},
beforeCreate() {
console.log('vm beforeCreate',document.querySelectorAll('li').length)
},
created() {
console.log('vm created',document.querySelectorAll('li').length)
},
beforeMount() {
console.log('vm beforeMount',document.querySelectorAll('li').length)
},
mounted() {
this.maxLen = 4
console.log('vm mounted',document.querySelectorAll('li').length)
},
beforeUpdate() {
console.log('vm beforeUpdate',document.querySelectorAll('li').length)
},
updated(){
console.log('vm beforeUpdate',document.querySelectorAll('li').length)
}
})
</script>
</body>
</html>
可以看到,在异步更改或初次渲染之后更改与子组件props绑定的响应式属性时,不但会执行父组件的beforeUpdate、updated钩子,还会执行子组件的beforeUpdate、updated钩子,而且在父组件的beforeUpdate、updated钩子之间执行。
为什么会这样呢?
那是因为更改maxLen,不但是更改了父组件的响应式属性,当maxLen值传到子组件的props时,也等于是更改了子组件的响应式属性,所以当然会先触发父组件的重新渲染,然后触发子组件的重新渲染。
父组件更改子组件的响应式属性
父组件可以异步更改或在初步渲染后(比如mounted中)非异步更改子组件的data或props的值:
<!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>vue生命周期学习</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<v-ul :max="maxLen" :style="{color:ulcolor}">
</v-ul>
</div>
<script>
Vue.component('v-ul', {
template: '\
<ul :style="{fontSize:size}">\
<li v-for="(item,index) in max" :key="index">{{item}}</li>\
</ul>\
',
props: ['max'],
data: function(){
return {"size":"18px"}
},
beforeCreate() {
console.log('ul beforeCreate',document.querySelectorAll('li').length)
},
created() {
console.log('ul created',document.querySelectorAll('li').length)
},
beforeMount() {
console.log('ul beforeMount',document.querySelectorAll('li').length)
},
mounted() {
console.log('ul mounted',document.querySelectorAll('li').length)
},
beforeUpdate() {
console.log('ul beforeUpdate',document.querySelectorAll('li').length)
},
updated(){
console.log('ul updated',document.querySelectorAll('li').length)
}
})
var vm = new Vue({
el: '#app',
data: {
maxLen: 3,
ulcolor: "dark"
},
beforeCreate() {
console.log('vm beforeCreate',document.querySelectorAll('li').length)
},
created() {
/*异步更改
var vm = this;
setTimeout(function () {
vm.$children[0].size = "12px"
})
*/
console.log('vm created',document.querySelectorAll('li').length)
},
beforeMount() {
console.log('vm beforeMount',document.querySelectorAll('li').length)
},
mounted() {
//更改子组件data中的size
this.$children[0].size = "12px"
//也可以更改子组件props中的max,但会抛错,因为父组件重新渲染时会覆盖此值
//this.$children[0].max = 4
console.log('vm mounted',document.querySelectorAll('li').length)
},
beforeUpdate() {
console.log('vm beforeUpdate',document.querySelectorAll('li').length)
},
updated(){
console.log('vm updated',document.querySelectorAll('li').length)
}
})
</script>
</body>
</html>
运行结果:
可以看到,更改子组件的响应式属性仅触发子组件的重新渲染。
子组件更改响应式属性
子组件更改自己的响应式属性
在初步渲染前(created或beforeMount中)非异步更改data或props的值并不会触发重新渲染;
在子组件内部异步更改或在初步渲染后(比如mounted中)非异步更改data或props的值会触发重新渲染。
将上面的例子重写如下:
<!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>vue生命周期学习</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<v-ul :max="maxLen" :style="{color:ulcolor}">
</v-ul>
</div>
<script>
Vue.component('v-ul', {
template: '\
<ul :style="{fontSize:size}">\
<li v-for="(item,index) in max" :key="index">{{item}}</li>\
</ul>\
',
props: ['max'],
data: function(){
return {"size":"18px"}
},
beforeCreate() {
console.log('ul beforeCreate',document.querySelectorAll('li').length)
},
created() {
/*异步方式
var vm = this;
setTimeout(function () {
vm.size = "13px"
})
*/
console.log('ul created',document.querySelectorAll('li').length)
},
beforeMount() {
console.log('ul beforeMount',document.querySelectorAll('li').length)
},
mounted() {
//更改data中的size
this.size = "12px"
//也可以更改props中的max,但会抛错,因为父组件重新渲染时会覆盖此值
//this.max = 4
console.log('ul mounted',document.querySelectorAll('li').length)
},
beforeUpdate() {
console.log('ul beforeUpdate',document.querySelectorAll('li').length)
},
updated(){
console.log('ul updated',document.querySelectorAll('li').length)
}
})
var vm = new Vue({
el: '#app',
data: {
maxLen: 3,
ulcolor: "dark"
},
beforeCreate() {
console.log('vm beforeCreate',document.querySelectorAll('li').length)
},
created() {
console.log('vm created',document.querySelectorAll('li').length)
},
beforeMount() {
console.log('vm beforeMount',document.querySelectorAll('li').length)
},
mounted() {
console.log('vm mounted',document.querySelectorAll('li').length)
},
beforeUpdate() {
console.log('vm beforeUpdate',document.querySelectorAll('li').length)
},
updated(){
console.log('vm updated',document.querySelectorAll('li').length)
}
})
</script>
</body>
</html>
运行结果:
可以看到,子组件在初次渲染之后更改子组件的响应式属性只会触发子组件的beforeUpdate、updated钩子。这和在父组件那边更改是一样的结果。
子组件更改父组件的响应式属性
在子组件中更改父组件的响应式属性和在父组件中更改结果是一致的:如果此属性是与子组件props绑定的,则父子组件都会重新渲染;不与子组件props绑定的,则仅父组件重新渲染。
放上更改父组件的与子组件props绑定的响应式属性的例子:
<!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>vue生命周期学习</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<v-ul :max="maxLen" :style="{color:ulcolor}">
</v-ul>
</div>
<script>
Vue.component('v-ul', {
template: '\
<ul :style="{fontSize:size}">\
<li v-for="(item,index) in max" :key="index">{{item}}</li>\
</ul>\
',
props: ['max'],
data: function(){
return {"size":"18px"}
},
beforeCreate() {
console.log('ul beforeCreate',document.querySelectorAll('li').length)
},
created() {
console.log('ul created',document.querySelectorAll('li').length)
},
beforeMount() {
console.log('ul beforeMount',document.querySelectorAll('li').length)
},
mounted() {
this.$parent.maxLen = 4
console.log('ul mounted',document.querySelectorAll('li').length)
},
beforeUpdate() {
console.log('ul beforeUpdate',document.querySelectorAll('li').length)
},
updated(){
console.log('ul updated',document.querySelectorAll('li').length)
}
})
var vm = new Vue({
el: '#app',
data: {
maxLen: 3,
ulcolor: "dark"
},
beforeCreate() {
console.log('vm beforeCreate',document.querySelectorAll('li').length)
},
created() {
console.log('vm created',document.querySelectorAll('li').length)
},
beforeMount() {
console.log('vm beforeMount',document.querySelectorAll('li').length)
},
mounted() {
console.log('vm mounted',document.querySelectorAll('li').length)
},
beforeUpdate() {
console.log('vm beforeUpdate',document.querySelectorAll('li').length)
},
updated(){
console.log('vm updated',document.querySelectorAll('li').length)
}
})
</script>
</body>
</html>
运行结果如下:
可以看到父子组件都重新渲染了。
在mounted中使用nextTick得到下次视图渲染结果
同时可以看到,当这种情况时,父子组件重新渲染发生在父组件的mounted钩子之后。所以在父组件的mounted钩子显示<li>
元素个数还是3。如果想在mounted钩子中得到父组件和所有子孙组件更改响应式数据后的下次视图渲染结果可以使用mounted钩子的一开始就使用nextTick:
mounted: function () {
this.$nextTick(function () {
console.log('vm mounted nextTick',document.querySelectorAll('li').length)
})
console.log('vm mounted',document.querySelectorAll('li').length)
}
运行后,你将会看到<li>
元素个数变成了4。
注意,这是下次整个视图渲染结果,但不包括异步情况,因为无论是异步更改响应式属性还是初始化异步子组件,都是异步任务,它们的视图渲染会在同步任务的视图渲染之后。 可查看本单节最后的例子以加深理解。
异步子组件
mounted 不会保证所有的子组件也都一起被挂载
在mounted API中说“mounted 不会保证所有的子组件也都一起被挂载”。为什么会这样呢?这是因为有一种子组件是异步组件,它并不会在父组件mounted钩子中被挂载,而是触发父组件的重新渲染,
在beforeUpdate、updated钩子之间完成初步渲染和挂载。
将上面的例子的v-ul子组件重写成异步子组件:
<!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>vue生命周期学习</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<v-ul :max="maxLen" :style="{color:ulcolor}">
</v-ul>
</div>
<script>
Vue.component('v-ul', function (resolve, reject) {
setTimeout(function () {
resolve({
template: '\
<ul :style="{fontSize:size}">\
<li v-for="(item,index) in max" :key="index">{{item}}</li>\
</ul>\
',
props: ['max'],
data: function(){
return {"size":"18px"}
},
beforeCreate() {
console.log('ul beforeCreate',document.querySelectorAll('li').length)
},
created() {
console.log('ul created',document.querySelectorAll('li').length)
},
beforeMount() {
console.log('ul beforeMount',document.querySelectorAll('li').length)
},
mounted() {
console.log('ul mounted',document.querySelectorAll('li').length)
},
beforeUpdate() {
console.log('ul beforeUpdate',document.querySelectorAll('li').length)
},
updated(){
console.log('ul updated',document.querySelectorAll('li').length)
}
})
})
})
var vm = new Vue({
el: '#app',
data: {
maxLen: 3,
ulcolor: "dark"
},
beforeCreate() {
console.log('vm beforeCreate',document.querySelectorAll('li').length)
},
created() {
console.log('vm created',document.querySelectorAll('li').length)
},
beforeMount() {
console.log('vm beforeMount',document.querySelectorAll('li').length)
},
mounted() {
console.log('vm mounted',document.querySelectorAll('li').length)
},
beforeUpdate() {
console.log('vm beforeUpdate',document.querySelectorAll('li').length)
},
updated(){
console.log('vm updated',document.querySelectorAll('li').length)
}
})
</script>
</body>
</html>
运行结果:
可以看到在父组件的beforeUpdate、updated钩子之间完成子组件的beforeCreate、created、beforeMount、mounted钩子。这说明在异步子组件初始化时,会触发父组件的重新渲染。
父子组件同时更改
updated 不会保证所有的子组件也都一起被重绘
如果子组件异步更改响应式属性,并且父组件也更改响应式属性呢?
例子:
<!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>vue生命周期学习</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<v-ul :max="maxLen" :style="{color:ulcolor}">
</v-ul>
</div>
<script>
Vue.component('v-ul', {
template: '\
<ul :style="{fontSize:size}">\
<li v-for="(item,index) in max" :key="index">{{item}}</li>\
</ul>\
',
props: ['max'],
data: function(){
return {"size":"18px"}
},
beforeCreate() {
console.log('ul beforeCreate')
},
created() {
console.log('ul created')
},
beforeMount() {
console.log('ul beforeMount')
},
mounted() {
setTimeout(()=>
this.size="12px"
)
console.log('ul mounted')
},
beforeUpdate() {
console.log('ul beforeUpdate')
},
updated(){
console.log('ul updated',this.size)
}
})
var vm = new Vue({
el: '#app',
data: {
maxLen: 3,
ulcolor: "dark"
},
beforeCreate() {
console.log('vm beforeCreate')
},
created() {
console.log('vm created')
},
beforeMount() {
console.log('vm beforeMount')
},
mounted() {
this.ulcolor="red"
console.log('vm mounted')
},
beforeUpdate() {
console.log('vm beforeUpdate')
},
updated(){
console.log('vm updated',this.$children[0].$el.style.fontSize)
}
})
</script>
</body>
</html>
可以看到,子组件的beforeUpdate、updated在父组件updated后面。所以父组件的updated钩子里面显示的还是子组件的重绘前的结果。
在updated中使用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>vue生命周期学习</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<v-ul :max="maxLen" :style="{color:ulcolor}">
</v-ul>
</div>
<script>
var v_ul_config = Vue.extend({
template: '\
<ul :style="{fontSize:size}">\
<li v-for="(item,index) in max" :key="index">{{item}}</li>\
</ul>\
',
props: ['max'],
data: function(){
return {"size":"18px"}
},
beforeCreate() {
console.log('ul beforeCreate')
},
created() {
console.log('ul created')
},
beforeMount() {
console.log('ul beforeMount')
},
mounted() {
this.size="1px"
console.log('ul mounted')
},
beforeUpdate() {
console.log('ul beforeUpdate')
},
updated(){
console.log('ul updated',this.size)
this.size="12px"
}
});
Vue.component('v-ul', v_ul_config)
var vm = new Vue({
el: '#app',
data: {
maxLen: 3,
ulcolor: "dark"
},
beforeCreate() {
console.log('vm beforeCreate')
},
created() {
console.log('vm created')
},
beforeMount() {
console.log('vm beforeMount')
},
mounted() {
this.ulcolor = "red";
console.log('vm mounted')
},
beforeUpdate() {
console.log('vm beforeUpdate')
},
updated(){
this.$nextTick(function () {
console.log('vm updated nextTick',this.$children[0].$el.style.fontSize)
})
console.log('vm updated',this.$children[0].$el.style.fontSize)
}
})
</script>
</body>
</html>
运行结果:
可以看到,不使用nextTick是无法得到子组件最新的渲染结果的。
响应式属性变化处理:mounted、beforeUpdate、updated、nextTick、watch的区别
对数据变化进行处理的方式 | 针对对象 | 哪些数据 | 哪种变化 | 处理时间 | 处理方式 | 限制 |
---|---|---|---|---|---|---|
mounted | Vue实例 | 所有响应式属性 | 在初次渲染前的变化 | 数据变化导致所关联的DOM初次渲染并挂载完成时 (不会保证所有的子组件也都一起渲染并被挂载) | 初次渲染只有一次, 所以是针对初次渲染后的一次性处理 | – |
beforeUpdate、updated | Vue实例 | 所有响应式属性 | 在初次渲染后、重新渲染前的变化 | 数据变化导致所关联的DOM重新渲染前后 (不会保证所有的子组件也都一起被重新渲染) | 重新渲染都会触发, 因此每次重新渲染都会处理, 所以是重新渲染前后的通用处理 | 避免更改响应式属性 |
nextTick | 整个视图 | 整个视图所有Vue实例的响应式属性 | 下一次整个视图渲染前的变化 | 数据变化导致下一次整个视图的DOM渲染完成时 | 下一次整个视图渲染后的一次性处理 | – |
watch | Vue实例 | 某个响应式属性 | 每次变化 | 数据变化后DOM渲染前处理业务逻辑,不能获取渲染后的DOM | 每次数据变化都会触发, 所以是针对某个响应式属性的通用处理 | 不能处理渲染后的DOM, 如想处理可以使用nextTick |
深刻理解nextTick、watch和生命周期的关系
例子
<!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>vue生命周期学习</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<v-ul :max="maxLen" :style="{color:ulcolor,fontSize:ulsize}">
</v-ul>
</div>
<script>
var v_ul_config = Vue.extend({
template: '\
<ul :style="{fontSize:size}">\
<li v-for="(item,index) in max" :key="index">{{item}}</li>\
</ul>\
',
props: ['max'],
data: function(){
return {"size":"18px"}
},
beforeCreate() {
console.log('ul beforeCreate')
},
created() {
console.log('ul created')
},
beforeMount() {
console.log('ul beforeMount')
},
mounted() {
console.log('ul mounted')
},
beforeUpdate() {
console.log('ul beforeUpdate')
},
updated(){
console.log('ul updated',this.size)
}
});
Vue.component('v-ul', v_ul_config)
var vm = new Vue({
el: '#app',
data: {
maxLen: 3,
ulcolor: "dark",
ulsize: "18px"
},
watch: {
ulcolor: function (newColor, oldColor) {
console.log('========= watch start ==========')
console.log('watch oldColor',oldColor)
console.log('watch newColor',newColor)
console.log('========= watch end =========')
}
},
beforeCreate() {
console.log('vm beforeCreate')
this.$nextTick(function () {
console.log('vm beforeCreate nextTick',this.$children[0].$el.style.color)
})
},
created() {
this.ulcolor = "yellow"
console.log('vm created')
this.$nextTick(function () {
console.log('vm created nextTick',this.$children[0].$el.style.color)
})
},
beforeMount() {
console.log('vm beforeMount')
this.$nextTick(function () {
console.log('vm beforeMount nextTick',this.$children[0].$el.style.color)
})
},
mounted() {
this.ulcolor = "green"
this.$nextTick(function () {
console.log('vm mounted nextTick, modify ulcolor to red...',this.$children[0].$el.style.color)
this.ulcolor = "red"
})
setTimeout(()=>{
this.ulcolor = "orange";
})
console.log('vm mounted')
},
beforeUpdate() {
console.log('vm beforeUpdate')
},
updated(){
this.$nextTick(function () {
console.log('vm updated nextTick',this.$children[0].$el.style.color)
})
console.log('vm updated',this.$children[0].$el.style.color)
this.ulcolor = "blue"
}
})
</script>
</body>
</html>
运行结果:
原理
JS代码执行流程
执行代码的流程为:
先执行同步任务(同步宏任务),再执行异步任务(异步宏任务)。
执行同步/异步任务的流程为:
先执行任务(宏任务)队列,再执行子任务(微任务)队列,最后浏览器UI接口渲染构成一个事件循环。
事件循环只能存在一个
一个页面只能存在一个事件循环,同步任务属于一个事件循环,异步任务属于一个新的事件循环(上一个事件循环结束)。
同步任务事件循环 -> 事件循环结束 -> 异步任务事件循环 -> 事件循环结束 -> 下一个异步任务事件循环 -> …
一个事件循环可以包含多个异步子任务队列/DOM更新循环/tick
一个事件循包含一个任务队列和零个或多个异步子任务队列/DOM更新循环/tick。 nextTick相当于在下一个异步子任务队列/DOM更新循环/tick要做的事
例子的运行逻辑
同步任务所属事件循环
--------------------------------------------------事件循环开始--------------------------------------------------
-------------同步任务队列-------------
同步任务队列,顺序执行任务
- beforeCreate
console.log('vm beforeCreate')
- created
this.ulcolor = "yellow"
console.log('vm created')
- beforeMount
console.log('vm beforeMount')
- 子组件beforeCreate、created、beforeMount、mounted
console.log('ul beforeCreate')
console.log('ul created')
console.log('ul beforeMount')
console.log('ul mounted')
- mounted
this.ulcolor = "green"
console.log('vm mounted')
beforeCreate 、created、beforeMount、mounted中的nextTick操作
-
调用nextTick => 回调函数作为异步子任务 => 转入nextTick的异步子任务队列
beforeCreate :console.log('vm beforeCreate nextTick',this.$children[0].$el.style.color)
-
此任务队列发生的响应式属性更新(一个任务队列watch只触发一次, mounted里面ulcolor数据变化: dark=>(created的yellow被mounted的green替换)=>green)=> DOM更新操作作为异步子任务 => 转入nextTick的异步子任务队列
created:this.ulcolor = "yellow"
mounted:this.ulcolor = "green"
触发DOM更新异步子任务 -
调用nextTick => 回调函数作为异步子任务 => 转入nextTick的异步子任务队列
created:console.log('vm created nextTick',this.$children[0].$el.style.color)
-
调用nextTick => 回调函数作为异步子任务 => 转入nextTick的异步子任务队列
beforeMount :console.log('vm beforeMount nextTick',this.$children[0].$el.style.color)
-
调用nextTick => 回调函数作为异步子任务 => 转入nextTick的异步子任务队列
mounted:
console.log('vm mounted nextTick, modify ulcolor to red...',this.$children[0].$el.style.color)
this.ulcolor = "red"
-------------同步任务队列结束-------------
-------------第一次DOM 更新循环(tick,异步子任务队列)-------------
异步子任务队列,顺序执行子任务
-
异步子任务1: 执行nextTick 回调函数
console.log('vm created nextTick',this.$children[0].$el.style.color)
-
异步子任务2:上次tick中mounted里面ulcolor数据变化: dark=>(created的yellow被mounted的green替换)=>green,DOM更新操作开始
侦听器(watch)
第一次重新渲染(beforeUpdate、updated) -
异步子任务3: 执行nextTick 回调函数
console.log('vm created nextTick',this.$children[0].$el.style.color)
-
异步子任务4: 执行nextTick 回调函数
console.log('vm beforeMount nextTick',this.$children[0].$el.style.color)
-
异步子任务5: 执行nextTick 回调函数
console.log('vm mounted nextTick, modify ulcolor to red...',this.$children[0].$el.style.color)
异步子任务2中的nextTick操作
-
此tick中调用nextTick => 回调函数作为异步子任务 => 转入nextTick的异步子任务队列
console.log('vm updated nextTick',this.$children[0].$el.style.color)
-
此tick中发生的响应式属性更新(一个任务队列watch只触发一次, nextTick里面ulcolor数据变化:green=>(updated的blue被nextTick的red替换)=> red)=> DOM更新操作作为异步子任务 => 转入nextTick的异步子任务队列
updated:this.ulcolor = "blue"
nextTick:this.ulcolor = "red"
触发DOM更新异步子任务 -
此tick中调用nextTick => 回调函数作为异步子任务 => 转入nextTick的异步子任务队列
console.log('vm mounted nextTick, modify ulcolor to red...',this.$children[0].$el.style.color)
-------------第一次DOM 更新循环结束 -------------
-------------第二次DOM 更新循环(tick,异步子任务队列)-------------
异步子任务队列,顺序执行子任务
-
异步子任务1: 执行nextTick 回调函数
console.log('vm updated nextTick',this.$children[0].$el.style.color)
-
异步子任务2:上次tick中ulcolor数据变化:green =>(updated的blue被nextTick的red替换)=> red,DOM更新操作开始
侦听器(watch)
第二次重新渲染(beforeUpdate、updated) -
异步子任务3:执行nextTick 回调函数
console.log('vm mounted nextTick, modify ulcolor to red...',this.$children[0].$el.style.color)
异步子任务2的nextTick操作
- 此tick中发生的响应式属性更新(updated里面ulcolor数据变化:red => blue)=> DOM更新操作作为异步子任务
=> 转入nextTick的异步子任务队列
updated:this.ulcolor = "blue"
触发DOM更新异步子任务
-------------第二次DOM 更新循环结束-------------
-------------第三次DOM 更新循环(tick,异步子任务队列)-------------
异步子任务队列,顺序执行子任务
- 异步子任务1:上次tick中updated里面ulcolor数据变化:red => blue,DOM更新操作开始
侦听器(watch)
第三次重新渲染(beforeUpdate、updated)
updated:this.ulcolor = "blue"
响应式属性保持不变,所以不再产生新的异步子任务队列。
-------------第三次DOM 更新循环结束-------------
--------------------------任务和子任务全部执行完毕,UI渲染后此事件循环结束 。----------------
异步任务所属事件循环
--------------------------------------------------事件循环开始--------------------------------------------------
-------------异步任务队列------------------
异步任务队列,顺序执行任务
- setTimeout里面ulcolor数据变化:ulcolor数据变化:blue => orange
this.ulcolor = "orange"
nextTick操作
- 发生响应式属性更新(setTimeout里面ulcolor数据变化: blue=>orange)=> DOM更新操作作为异步子任务 => 转入nextTick的异步子任务队列
setTimeout:this.ulcolor = "orange"
触发DOM更新异步子任务
-------------异步任务队列结束 ------------
-------------第四次DOM 更新循环(tick, 异步子任务队列)-------------
异步子任务队列,顺序执行子任务
- setTimeout里面ulcolor数据变化:blue => orange,DOM更新操作开始
侦听器(watch)
第四次重新渲染(beforeUpdate、updated)
nextTick操作
- 此tick中发生的响应式属性更新(updated里面ulcolor数据变化:ulcolor数据变化:orange => blue)=> DOM更新操作作为异步子任务 => 转入nextTick的异步子任务队列
updated:this.ulcolor = "blue"
触发DOM更新异步子任务
-------------第四次DOM 更新循环结束 -------------
-------------第五次DOM 更新循环(tick, 异步子任务队列)-------------
异步子任务队列,顺序执行子任务
- 异步子任务1:上次tick中updated里面ulcolor数据变化:orange => blue,DOM更新操作开始
侦听器(watch)
第五次重新渲染(beforeUpdate、updated)
updated:this.ulcolor = "blue"
响应式属性保持不变,所以不再产生新的异步子任务队列。
-------------第五次DOM 更新循环结束 -------------
--------------------------任务和子任务全部执行完毕,UI渲染后此事件循环结束 。----------------
本章节教程结束。
全部教程地址:Vue入门实战教程 | 寒于水学习网
vue生命周期实战系列: