问题:
在setup 使用aysnc,生命函数钩子和函数必须出现在await 语句前面,否者会出现组件无法渲染以及内存泄漏的问题。
import { ref, watch, onMounted, onUnmounted } from 'vue'
export default defineAsyncComponent({
async setup() {
const counter = ref(0)
watch(counter, () => console.log(counter.value))
onMounted(() => console.log('Mounted'))
// the await statement
await someAsyncFunction() // <-----------
// 无法运行
onUnmounted(() => console.log('Unmounted'))
//无法自动销毁导致内存泄漏(no auto-dispose)
watch(counter, () => console.log(counter.value * 2))
}
})
在await 语句使用以下函数:
有限制(no auto-dispose):
- watch / watchEffect
- computed
- effect
无法运行: - onMounted / onUnmounted / onXXX
- provide / inject
- getCurrentInstance
- …
产生bug的原因
onMounted 这个钩子注册了组件挂载的监听器
is a hook that registers a listener when the current component gets mounted.
在composition api中,lifecycle钩子函数是当作全局函数使用的,以onMouted 为例,
onMounted 这个钩子监听到组件挂载才会执行注册在onMounted 内的函数。
那么onMounted 是如何监听,在vue内部框架中,要挂载组件component时,会将组件component存储为全局变量。
伪代码:
let currentInstance = null
export function mountComponent(component) {
const instance = createComponent(component) // 组件实例存至全局变量
currentInstance = instance
//组件setup调用
component.setup()
}
setup伪代码:
setup() {
function onMounted(fn) {
if (!currentInstance) {
warn(`"onMounted" can't be called outside of component setup()`)
return
}
// 在当前组件上注册onMounted函数
currentInstance.onMounted(fn)
}
}
但是造成async bug的原因是什么,首先js是个单线程语言,执行完一行代码再执行下一行代码。
而异步的话会引入下面这种情况:
//伪代码
currentInstance = instance
await component.setup()
currentInstance = prev
在await 时间,其他组件的创建可能会修改全局变量,这样最后会导致一团混乱。
如果不用await 语句,像普通函数一样调用会产生什么结果?
async function setup() {
console.log(1)
await someAsyncFunction()
console.log(2)
}
console.log(3)
setup()
console.log(4)
// output: 3 1 4 (awaiting) 2
这种方法导致await还未执行完,就执行下一行。同样无法正确绑定组件。
解决方案
解决方案一:
await语句放到setup函数最下面
解决方案二:
1.将async转成sync reactive
//await
const data = await fetch('https://xxx.com/').then(r => r.json())
const user = data.user
//修改:
const data = ref(null) fetch('https://xxx.com/')
.then(r => r.json())
.then(res => data.value = res)
const user = computed(() => data?.user)
2.显示绑定实例
生命周期钩子函数是可以接受第二个参数(组件实例)。
async setup() {
const instance = getCurrentInstance()
await someAsyncFunction() // <-----------
onUnmounted(() => console.log('Unmounted'),
instance // <--- pass the instance to it
)
}
但是这里有个缺陷,除钩子函数像watch ,computed 等是没法接受示例参数。
3.使用vueuse提供的工具函数
useAsyncState :
import { useAsyncState } from '@vueuse/core'
const { state, ready } = useAsyncState(async () => {
const { data } = await axios.get('https://api.github.com/')
return { data }
}
)
const user = computed(() => state?.user)
useFetch
import { useFetch } from '@vueuse/core'
const { data, isFetching, error } = useFetch('https://api.github.com/')
const user = computed(() => data?.user)
解决方案三:
vue社区有个提案,提供一个aysnc语法糖withAsyncContext ,这样就能在setup中使用async语法。