問題#
在使用異步的 setup () 時,必須在第一個 await 陳述式之前使用 effects 和生命週期鉤子。(詳情)
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('Mounted'))
// the await statement
await someAsyncFunction() // <-----------
// does NOT work!
onUnmounted(() => console.log('Unmounted'))
// still works, but does not auto-dispose
// after the component is destroyed (memory leak!)
watch(counter, () => console.log(counter.value * 2))
}
})
在 await 陳述式之後,以下函數將受到限制(無自動處理):
watch
/watchEffect
computed
effect
以下函數將無法運作:
onMounted
/onUnmounted
/onXXX
provide
/inject
getCurrentInstance
限制#
異步函數在更新跨多個組件共享的全局變量時可能會引起問題。當等待 setup()
時,多個組件的創建可以更改全局變量,使其變得不可預測並在系統中造成混亂。如果 setup()
在沒有 await 陳述式的情況下被調用,它將在第一個 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