问题#
在使用异步的 setup()
时,必须在第一个 await
语句之前使用效果和生命周期钩子。(详情)
import { ref, watch, onMounted, onUnmounted } from 'vue'
export default defineAsyncComponent({
async setup() {
const counter = ref(0)
watch(counter, () => console.log(counter.value))
// OK!
onMounted(() => console.log('已挂载'))
// await 语句
await someAsyncFunction() // <-----------
// 不起作用!
onUnmounted(() => console.log('已卸载'))
// 仍然可以工作,但不会自动销毁
// 组件被销毁后会有内存泄漏!
watch(counter, () => console.log(counter.value * 2))
}
})
在 await
语句之后,以下函数将受到限制(不会自动销毁):
watch
/watchEffect
computed
effect
以下函数将不起作用:
onMounted
/onUnmounted
/onXXX
provide
/inject
getCurrentInstance
限制#
异步函数在更新跨多个组件共享的全局变量时可能会引发问题。当等待 setup()
时,多个组件的创建可能会更改全局变量,使其变得不可预测并在系统中造成混乱。如果在没有 await
语句的情况下调用 setup()
,它将在第一个 await
之前执行任务,影响执行的一致性。Vue 无法预测异步部分何时从外部调用,因此无法将实例绑定到上下文。
currentInstance = instance
await component.setup() // 原子性丢失
currentInstance = prev
async function setup() {
console.log(1)
await someAsyncFunction()
console.log(2)
}
console.log(3)
setup()
console.log(4)
// 输出:
3
1
4
(awaiting)
2
解决方案#
将异步函数包装为 “响应式同步”#
从
const data = await fetch('https://api.github.com/').then(r => r.json())
const user = data.user
到
const data = ref(null)
fetch('https://api.github.com/')
.then(r => r.json())
.then(res => data.value = res)
const user = computed(() => data?.user)
显式绑定实例#
import { effectScope, onMounted } from 'vue'
export default defineAsyncComponent({
async setup() {
// 在 `await` 之前获取并保存实例
const instance = getCurrentInstance()
// 在 `await` 之前创建作用域,以便将其绑定到实例
const scope = effectScope()
const data = await someAsyncFunction() // <-----------
onMounted(
() => {},
instance // <--- 将实例传递给它
)
scope.run(() => {
/* 使用 `computed`、`watch` 等... */
})
// 生命周期钩子在此处将不可用
}
})
参考:
Anthony Fu: https://antfu.me/posts/async-with-composition-api