vue3的composition API如何使用async语句

发布时间 2023-06-11 09:52:53作者: Alice_Fincher

问题:
在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语法。