Vue生命周期钩子和异步编程
Vue生命周期分为4部分:
- 创建(Initialization)
beforeCreate
created
- 挂载(DOM Insertion)
onBeforeMount
onMounted
- 更新(Data Changes)
onBeforeUpdate
onUpdated
- 销毁(Teardown)
onBeforeUnmount
onUnmounted
但是,在组合式API中,因为使用setup()
函数的缘故,创建这一生命周期钩子不再独立存在
而setup()
函数作为在beforeCreate
之后、created
之前运行的函数,取代了原先创建生命周期的功能
实现了Vue 3 组合式 API 设计的核心理念之一:将代码按逻辑功能而非生命周期阶段组织
作为新手,你肯定觉得了解这些有什么作用,完全不是我要处理的范畴,实际上,了解生命周期对于一个Vue的项目数据流向逻辑至关重要
案例1:浏览器控制台误判
<script setup>
const messages = ref([])
console.log('初始化:',JSON.parse(JSON.stringify(messages.value)))
onMounted(() => {
messages.value.push(
{content: '你无敌了孩子'}
);
console.log('挂载后:',JSON.parse(JSON.stringify(messages.value)))
})
</script>
你如果将JSON.parse(JSON.stringify(messages.value))
修改为messages.value
时,你会发现,这两个结果居然是一样的
这是因为浏览器中的console使用的proxy
指向,这就导致了使用push
实际上推送了数据但是没有修改索引,那么理所当然的二者显示的是同样的内容了
所以使用JSON.parse(JSON.stringify(messages.value))
输出字符串作为快照保存是一种合理的手段
案例2:如果再多一个console.log()
<script setup>
const messages = ref([])
console.log('初始化:',JSON.parse(JSON.stringify(messages.value)))
onMounted(() => {
messages.value.push(
{content: '你无敌了孩子'}
);
console.log('挂载后:',JSON.parse(JSON.stringify(messages.value)))
})
console.log('New:',JSON.parse(JSON.stringify(messages.value)))
</script>
这一次,我们在onMounted
后再加上一个console.log('New:',JSON.parse(JSON.stringify(messages.value)))
会发生什么呢
让我们想一想,如果按照书写顺序执行完onMounted
后,messages.value
已经是{content: '你无敌了孩子'}
,那么理所当然新加的console
输出内容也是一样的
但是,再看看Vue生命周期和setup()
函数,你会发现onMounted
应当在创建周期之后,而setup()
函数正好是组合式API中跨越创建周期的函数,那么实际上console.log('New:',JSON.parse(JSON.stringify(messages.value)))
是在onMounted
之前就执行的
所以正确的结果应该是空数组[]
这里,我们需要明白setup()
函数实际上就是一个同步函数,所有内部操作都是顺序执行,那你会问了:这个onMounted()
按顺序执行不应该在console.log('New:',JSON.parse(JSON.stringify(messages.value)))
执行吗?对的,确实在这个新加日志之前执行,但是onMounted()
实际上是一个生命周期钩子注册函数,也就是说使用onMounted()
只是声明我要使用这个钩子,但这个钩子只有到属于其生命周期的环节才执行
这一部分可以去了解回调函数的概念
最后的结果顺序其实是:‘初始化’、‘New’、‘挂载后’
但是说了这么多,好像没有看出来生命周期钩子跟异步编程有啥关系啊,你这上面的内容也没用到异步编程啊
有的,有的,兄弟,像这样的案例我还有10个!
案例3:顶层异步操作
<script setup>
import {onMounted, ref} from "vue";
async function pauseOperation() {
await delay(2000); // 挂起2秒
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const messages = ref([{content:'你来打我啊'}])
console.log('初始化:', JSON.parse(JSON.stringify(messages.value)))
onMounted(() => {
messages.value.push(
{content: '你无敌了孩子'}
);
console.log('挂载后:', JSON.parse(JSON.stringify(messages.value)))
})
console.log('New:', JSON.parse(JSON.stringify(messages.value)))
await pauseOperation()
</script>
如果真这么做了,会报错[Vue warn]: Component <Anonymous>: setup function returned a promise, but no <Suspense> boundary was found in the parent component tree. A component with async setup() must be nested in a <Suspense> in order to be rendered.
这是提示你,你让同步函数setup()
进行了Promise
的异步操作,通常情况下是不被允许的,如果有需要,需要保证setup()执行内容被<Suspense>
标签包括
关于异步setup()
的使用请读者自行搜索
你肯定想问,这都啥跟啥啊
但其实这里就已经涉及到你应用设计的逻辑了,通常情况下,我们推荐是将组件渲染出来再向里面填充数据,这是我们就可以异步获取数据的同时不必阻塞主线程(渲染流程)
比如我们可以将onMounted
修改为异步函数:如下
<script setup>
import {onMounted, ref} from "vue";
async function pauseOperation() {
await delay(2000); // 挂起2秒
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const messages = ref([{content:'你来打我啊'}])
console.log('初始化:', JSON.parse(JSON.stringify(messages.value)))
onMounted(async () => {
await pauseOperation()
messages.value.push(
{content: '你无敌了孩子'}
);
console.log('挂载后:', JSON.parse(JSON.stringify(messages.value)))
})
console.log('New:', JSON.parse(JSON.stringify(messages.value)))
</script>
这样,你就可以看到组件正常渲染,优先显示出'你来打我啊'
,经过pauseOperation()
挂起2s后,追加'你无敌了孩子'
的显示
关于生命周期钩子还有更多内容等待读者自己去尝试发掘,这里笔者只是给出一点概念性的小问题