1. 引言:Vue 3 的异步更新,我们真的了解吗?
在日常开发中,我们都知道 Vue 3 是异步更新的。大多数时候,我们只是简单地使用 ref
、reactive
、computed
等 API,享受着 Vue 的响应式系统带来的便利。然而,当我们在某些场景,忽略“异步”更新的底层逻辑,就会导致和预期不相符的问题。
最近,我在一个项目中遇到了一个关于 computed
的问题,这让我重新审视了 Vue 3 的异步更新机制。下面,我将分享这段经历,希望能帮助大家更好地理解 Vue 3 的异步更新策略。
2. 问题背景:computed
的二次转换与异步更新的冲突
在项目中,我需要对一个响应式数据进行二次转换,以便在模板中使用。为了分离逻辑,我使用了 computed
来实现这个转换。
整个问题涉及到3个文件:
index.vue
Temp.vue
useSearchParam.ts
在index.vue中,使用Temp.vue模块和useSearchParam。然后在index.vue中,通过computed,来处理和searchParam相关的转换逻辑。
代码大致如下:
index.vue
<template><Temp ref="tempRef" :param="computedParam"/>
</template>
<script setup lang="ts">
...
const { searchParam } = useSearchParam(()=>{tempRef.value.load();
});
const computedParam = computed(() => {return {businessCode: `computed businessCode:${searchParam.businessCode}`};
});
</script>
在 Temp.vue
组件中,我通过 props
接收 computedParam
,并在 load
方法中打印 businessCode
:
Temp.vue
<template><span>{{ param.businessCode }}</span>
</template>
<script lang="ts" setup>
interface Props {param: any
}
const props = defineProps<Props>();
const load = () => {console.log("temp-load-1", props.param.businessCode);...
};
defineExpose({ load });
</script>
在 useSearchParam.ts
中,我通过异步加载数据并更新 searchParam
:
useSeachParam.ts
export function useSearchParam(dataLoaded: Function) {const searchParam = reactive({});onMounted(async () => {const result = await store.businessNames.load({}));searchParam["businessCode"] = first(result)?.code;dataLoaded && dataLoaded();});
}
然而,当 businessNames
加载完成后,temp-load-1
打印出来的 businessCode
是 undefined
,而在span中却正常渲染出来了businessCode的值。这让我感到困惑,为什么没有获取到最新的 computedParamcomputed
值?
3. 问题剖析:computed
的惰性求值与异步更新的冲突
通过分析,我发现问题的根源在于 computed
的惰性求值机制。computed
只有在被访问时才会重新计算,而在异步更新中,computed
不会立即重新计算,导致在同步代码中获取到旧的值。
具体来说,当 searchParam.businessCode
被更新时,Vue 会标记 computedParam
为“脏”状态,但不会立即重新计算。只有在 computedParam
被访问时,Vue 才会重新计算并返回最新的值。然而,dataLoaded是一个同步函数(关键之处),当它触发了Temp里面的load方法时,computedParam
还没有被重新计算,导致 props.param.businessCode
仍然是 undefined
。
4. 解决方案:使用 nextTick
确保 computed
的更新
为了解决这个问题,我调整了 useSearchParam.ts
中的代码,将 dataLoaded
回调函数放入 nextTick
中执行:
onMounted(async () => {const result = (await store.businessNames.load({})) as BusinessNameItem[];searchParam["businessCode"] = first(result)?.code;nextTick(() => {dataLoaded && dataLoaded();});
});
通过这种方式,dataLoaded
回调函数会在 Vue 的下一个 tick 中执行,此时 searchParam
的更新已经完成,computedParam
也已经重新计算,并返回了最新的值。因此,temp-load-1
和 里面一样,都显示出来了相同的正常值。
5. 深入理解:Vue 3 的 tick
机制
通过这次问题的解决,我深入理解了 Vue 3 的 tick
机制。tick
是 Vue 完成一次响应式数据更新和 DOM 渲染的过程。Vue 会将所有的响应式数据更新和 DOM 更新批量处理,并在下一个事件循环的 tick 中执行。
在 nextTick
中,Vue 会完成所有响应式数据的更新和 DOM 的渲染,确保回调函数在最新的状态下执行,具体到这个问题中,computedParam已经完成了更新。因此,通过 nextTick
,我们可以确保在异步更新后获取到最新的computedParam的 computed
值。
6. 总结:从同步到异步,理解 Vue 3 的异步更新策略
通过这次经历,我们不仅解决了 computed
的异步更新问题,还深入理解了 Vue 3 的 tick
机制。Vue 3 的异步更新策略虽然复杂,但通过合理使用 nextTick
,我们可以确保在异步更新后获取到最新的数据。
在日常开发中,我们应该更加关注 Vue 3 的异步更新策略,尤其是在使用 computed
和 watch
时,确保数据的更新和渲染顺序正确。通过这种方式,我们可以编写出更加高效和可靠的 Vue 3 代码。