vue提供的computed是不支持异步的,而vueuse的computedAsync支持
使用场景:联动下拉框、搜索等地方,输入关键词触发ajax请求接口,用户输入的过程中取消上一次ajax请求
1、使用demo
vue
<template>
<div>{{ userInfo }}<button @click="handleChange">改变</button></div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { computedAsync } from '@vueuse/core'
function mockAjax (params) {
return new Promise(resolve => {
setTimeout(() => {resolve({code:0,data: {name:params}})}, 1000)
})
}
const name = ref('jack')
const userInfo = computedAsync(
async () => {
return await mockAjax(name.value)
},
null, // initial state
)
function handleChange () {
name.value = 'lucy'
}
</script>
2、参数
2.1 第3个参数可以接收一个boolean类型,判断异步是否处于进行中
这样就可以方便的实现请求中出现loading的效果
ts
import { ref } from 'vue'
import { computedAsync } from '@vueuse/core'
const evaluating = ref(false) // 可以用来做loading状态
const userInfo = computedAsync(
async () => { /* your logic */ },
null,
evaluating,
)
也可以接受一个options,配置各种信息,如下:
ts
{
lazy: boolean; // 是否懒执行
evaluating: boolean; // 是否处理中
shallow: boolean; // 是否使用shallow
onError: (e: unknown) => void // 当捕获到错误时的回调函数
}
2.2 取消回调
在第1个参数可以接收一个onCancel回调,当上一个异步还在处理中的时候,如果执行了computedAsync的,就会触发onCancel回调
vue
<template>
<div>{{evaluating}} / {{ userInfo }}<button @click="handleChange">改变</button></div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { computedAsync } from '@vueuse/core'
function mockAjax (params) {
return new Promise(resolve => {
setTimeout(() => {resolve({code:0,data: {name:params}})}, 1000)
})
}
const name = ref('jack')
const evaluating = ref(false)
const userInfo = computedAsync(
async (onCancel) => {
onCancel(() => {
console.log('canceled'); // 在这里,我们可以通过取消axios来取消上一次请求的,demo可见:https://vueuse.org/core/computedAsync/#oncancel
});
return await mockAjax(name.value)
},
null, // initial state
evaluating
)
function handleChange () {
name.value = name.value +'j';
}
</script>
2.3 lazy懒执行
默认情况下,computedAsync
会在创建时立即开始解析,指定 lazy: true
可使其在第一次访问时开始解析。
vue
<template>
</template>
<script lang="ts" setup>
const userInfo = computedAsync(
async (onCancel) => {
onCancel(() => {
console.log('canceled')
});
console.log('发起ajax');
return await mockAjax(name.value)
},
null,
{lazy: true, evaluating}
)
</script>
想上面代码中只有定义了userInfo但没有使用,则 "发起ajax" 是不会执行的
3、源码
ts
export function computedAsync(evaluationCallback, initialState, optionsOrRef) {
let options
// 磨平第3个参数传参不同的差异,并解构options配置
// 只传了一个值,就是处理 computedAsync(()=>{},null,ref(false))第3个参数是一个ref的情况
if (isRef(optionsOrRef)) {
options = {
evaluating: optionsOrRef,
}
}
else {
options = optionsOrRef || {}
}
const {
lazy = false,
evaluating = undefined,
shallow = true,
onError = noop,
} = options
const started = ref(!lazy)
const current = (shallow ? shallowRef(initialState) : ref(initialState)) as Ref<T>
let counter = 0
watchEffect(async (onInvalidate) => {
// 当传了 {lazy:true} started=false,初次执行到这里就停了
// 直到有地方调用结果.value,就会触发后面的computed,里面会设置started=true
if (!started.value)
return
counter++
const counterAtBeginning = counter
let hasFinished = false
// Defer initial setting of `evaluating` ref
// to avoid having it as a dependency
// 推迟初始设置 `evaluating` ref
// 以避免将其作为依赖项
if (evaluating) {
Promise.resolve().then(() => {
evaluating.value = true
})
}
try {
const result = await evaluationCallback((cancelCallback) => {
onInvalidate(() => {
if (evaluating)
evaluating.value = false
// 如果上一个还没执行完成,就执行cancelCallback,可以在里面取消上一次异步请求
if (!hasFinished)
cancelCallback()
})
})
// 当上一次异步还没执行完,依赖项发生改变就会再调一次evaluationCallback()
// counterAtBeginning是当前第N次执行,counter是最后第N次,当2个值相同的时候,说明是最后一次执行了
if (counterAtBeginning === counter)
current.value = result
}
catch (e) {
onError(e)
}
finally {
if (evaluating && counterAtBeginning === counter)
evaluating.value = false
hasFinished = true
}
})
if (lazy) {
return computed(() => {
started.value = true
return current.value
})
}
else {
return current
}
}