只有同步代码的情况
本文将给不同的生命周期执行的打印操作,来观察生命周期中代码执行的顺序,同时加深对数据的产生和变化的理解。 首先我们来看一下,给前4个钩子都添加控制台打印的代码,
<script>
export default {
name: "App",
beforeCreate() {
console.log("beforeCreate运行");
},
created() {
console.log("created开始 ");
},
beforeMount() {
console.log("beforeMount运行");
},
mounted() {
console.log("mounted开始 ");
},
};
</script>
其执行结果是
接下来我们添加数据,并进行修改操作来查看 beforeUpdated 和 updated 这两个钩子,我们先不将数据渲染进视图,观察改变数据会不会触发这两个钩子
<template>
<div id="app">生命周期测试</div>
</template>
<script>
export default {
name: "App",
data() {
return {
test: 0,
};
},
beforeCreate() {
this.test++
console.log("beforeCreate运行"+this.test);
},
created() {
this.test++
console.log("created开始 "+this.test);
},
beforeMount() {
this.test++
console.log("beforeMount运行"+this.test);
},
mounted() {
this.test++
console.log("mounted开始 "+this.test);
},
beforeUpdate() {
this.test++
console.log("beforeUpdate运行"+this.test);
},
updated() {
this.test++
console.log("updated开始"+this.test);
}
};
</script>
打印结果
我们可以看到 beforeUpdate 和 updated 这两个钩子没有被触发,很显然,这两个钩子是监视视图的。同时,我们看到,在 beforeCreate 中获取不到数据,顾名思义。
接下来,我们在视图中渲染数据
<template>
<div id="app">生命周期测试{{test}}</div>
</template>
<script>
export default {
name: "App",
data() {
return {
test: 0,
};
},
beforeCreate() {
this.test++
console.log("beforeCreate运行"+this.test);
},
created() {
this.test++
console.log("created开始 "+this.test);
},
beforeMount() {
this.test++
console.log("beforeMount运行"+this.test);
},
mounted() {
this.test++
console.log("mounted开始 "+this.test);
},
beforeUpdate() {
this.test++
console.log("beforeUpdate运行"+this.test);
},
updated() {
this.test++
console.log("updated开始"+this.test);
}
};
</script>
哈哈,不幸的是,如果你运行了上面的代码,你会发现进入了死循环,因为你在视图更新前又要去改数据,视图更新后也要改数据,改了数据又要触发这两个钩子,触发了钩子又要改,这可不就是进入循环了吗。所以,这里我们得到一个教训就是,千万不要在 beforeUpdate 和 updated 中修改影响视图的数据! 现在我们去掉这两条修改数据的代码 代码链接
<template>
<div id="app">生命周期测试{{test}}</div>
</template>
<script>
export default {
name: "App",
data() {
return {
test: 0,
};
},
beforeCreate() {
this.test++
console.log("beforeCreate运行"+this.test);
},
created() {
this.test++
console.log("created开始 "+this.test);
},
beforeMount() {
this.test++
console.log("beforeMount运行"+this.test);
},
mounted() {
this.test++
console.log("mounted开始 "+this.test);
},
beforeUpdate() {
console.log("beforeUpdate运行"+this.test);
},
updated() {
console.log("updated开始"+this.test);
}
};
</script>
打印结果
我们看到 beforeUpdate 和 updated 都只被触发了一次,明明我们前面的4个钩子至少修改了3次 test 啊,怎么只更新了一次呢?视图的更新肯定是在挂载之后的,所以在本例中 mounted 中的修改才会触发视图更新,因为挂载前,是虚拟dom,还没渲染进视图。
异步:模拟有请求的情况
我们都知道,Vue 的 created 和 mounted 这两个钩子比较适合进行 AJAX 请求。接下来我们来看一下,在 created 和 mounted 中各自用 Pormise 来异步修改一个数据,看看结果是怎样的。 代码链接
<template>
<div id="app">生命周期测试{{test}}</div>
</template>
<script>
export default {
name: "App",
data() {
return {
test: 0
};
},
beforeCreate() {
this.test++;
console.log("beforeCreate运行" + this.test);
},
created() {
this.test++;
console.log("created开始 " + this.test);
new Promise((resolve, reject) => {
resolve();
}).then(() => {
this.test++;
console.log("异步的created开始 " + this.test);
});
},
beforeMount() {
this.test++;
console.log("beforeMount运行" + this.test);
},
mounted() {
this.test++;
console.log("mounted开始 " + this.test);
new Promise((resolve, reject) => {
resolve();
}).then(() => {
this.test++;
console.log("异步的mounted开始 " + this.test);
});
},
beforeUpdate() {
console.log("beforeUpdate运行" + this.test);
},
updated() {
console.log("updated开始" + this.test);
}
};
</script>
我们发现,updated 这个钩子只被触发了2次,我们看到,两次使用 Promise 修改数据都是在 mounted 之后执行的,所以,mounted之后至少修改了 3 次数据,显然有 2 次修改的视图更新被合并了,因为 Vue 是异步渲染的,像是有个周期一样,每个周期内收集需要修改的内容,然后时间到了就渲染,并清空渲染队列,再进入下一个周期。在本例中,渲染 mounted 中的修改和 created 中的异步修改被合并了。所以可以认为,在 created 中请求数据,也不会立即得到,至少需要执行到 mounted 之后中才能获取到。
watch 的 immediate 属性与生命周期的关系
接下来我们来聊一下 watch 中的 immediate 这个属性。假如我们的项目中的有一个 tabs 切换不同的显示,每个 tab 对应一个 id 以及对应的数据,数据需要根据 id 向后台去请求,我们需要 watch 来监听这个 id,当它一变化,也就是我们切换了 tab,需要重新请求数据。比如:
watch: {
id: {
async handler() {
const res = await this.$http.get('/data/' + id)
this.data = res.data
}
}
},
但是,这种方案,会有一个问题,就是 watch 的immediate 为 false 的时候,id 从 undefined 到初始化完成的时候,是不会监听的,也就是我们初次打开页面的时候,没法获取到 tab 的数据。时候可以通过将 immediate 设为 true 来解决。下面举例来说明这个属性的影响。
首先,我们来看一下 watch 设置 immediate 为 false 时候,在各钩子中变化数据的反应。 代码链接
<template>
<div id="app">生命周期测试{{test}}</div>
</template>
<script>
export default {
name: "App",
data() {
return {
test: 0
};
},
beforeCreate() {
this.test++;
console.log("beforeCreate运行" + this.test);
},
created() {
this.test++;
console.log("created开始 " + this.test);
new Promise((resolve, reject) => {
resolve();
}).then(() => {
this.test++;
console.log("异步的created开始 " + this.test);
});
},
beforeMount() {
this.test++;
console.log("beforeMount运行" + this.test);
},
mounted() {
this.test++;
console.log("mounted开始 " + this.test);
new Promise((resolve, reject) => {
resolve();
}).then(() => {
this.test++;
console.log("异步的mounted开始 " + this.test);
});
},
beforeUpdate() {
console.log("beforeUpdate运行" + this.test);
},
updated() {
console.log("updated开始" + this.test);
},
watch: {
test: {
handler(oldVal,newVal) {
console.log('watch 到 test 变化了');
}
},
},
};
</script>
结果
我们可以看到,在 mounted 之前修改的数据 根本 watch不到,mounted以及之后的数据修改的 watch 情况与 update 是同步的。其实渲染中也是有一个 watch 的,如果数据修改了,watch就会被压入队列中,如果同一个 watch 在一个 nextTick 的状态为等待时间时多次被触发,也会去重,不会多次压入队列,所以,watch 也是有一个异步调用的机制在里面的。
接下来,我们将 watch 设置 immediate 为 true 代码链接
<template>
<div id="app">生命周期测试{{test}}</div>
</template>
<script>
export default {
name: "App",
data() {
return {
test: 0
};
},
beforeCreate() {
this.test++;
console.log("beforeCreate运行" + this.test);
},
created() {
this.test++;
console.log("created开始 " + this.test);
new Promise((resolve, reject) => {
resolve();
}).then(() => {
this.test++;
console.log("异步的created开始 " + this.test);
});
},
beforeMount() {
this.test++;
console.log("beforeMount运行" + this.test);
},
mounted() {
this.test++;
console.log("mounted开始 " + this.test);
new Promise((resolve, reject) => {
resolve();
}).then(() => {
this.test++;
console.log("异步的mounted开始 " + this.test);
});
},
beforeUpdate() {
console.log("beforeUpdate运行" + this.test);
},
updated() {
console.log("updated开始" + this.test);
},
watch: {
test: {
handler(oldVal, newVal) {
console.log("watch 到 test 变化了");
},
immediate: true
}
}
};
</script>
结果
我们发现,在 mounted 之前,test 被初始化的时候,触发了一次 watch。所以,如果需要根据一个变化进行请求的情况,可以考虑把 immediate 设为true。