
本文详解 Pinia 组合式 API(setup store)中异步动作的正确 await 方式,重点解决因错误解构 ref 导致的响应性丢失、undefined 返回值及 promise 未正确等待等问题,并提供可直接落地的修复方案与最佳实践。
本文详解 pinia 组合式 api(setup store)中异步动作的正确 await 方式,重点解决因错误解构 `ref` 导致的响应性丢失、`undefined` 返回值及 promise 未正确等待等问题,并提供可直接落地的修复方案与最佳实践。
在使用 Pinia 的组合式 API(即 defineStore 配合 setup() 函数)时,一个常见但极易被忽视的陷阱是:对 store 内部 ref 响应式变量进行对象解构(如 const { widgets } = dashboardStore),会破坏其响应性连接,导致后续读取为 undefined 或无法反映最新状态。这正是你遇到 values is undefined 错误的根本原因——getWidgets() 中解构出的 widgets 已不再是响应式引用,且 loadWidgets() 返回的是 useFetch 的 Promise 包装对象,而非实际数据;而你又试图在 .then() 中访问已失效的 widgets.value。
✅ 正确做法:保持响应式引用 + 显式等待 fetch 完成
首先,修正 store 中 loadWidgets 的实现:useFetch 默认返回一个包含 data、pending、Error 等属性的对象,它本身不自动 resolve 数据。你需要显式 await 其内部的 data(或通过 execute() 触发请求并等待完成)。同时,避免解构 ref 变量。
✅ 修复后的 dashboard.js(store)
import { defineStore } from "pinia"; import { useFetch } from "#app"; import { useAuthStore } from "./auth"; const baseUrl = import.meta.env.VITE_API_KEY + "/hr/widgets/dashboard"; export const useDashboardStore = defineStore("dashboard", () => { const authStore = useAuthStore(); const { getToken } = authStore; const widgets = ref([]); const fetchedWidgets = ref(false); // ✅ 改为返回一个可 await 的 Promise,显式 resolve 响应数据 async function loadWidgets() { const { data, error, execute } = useFetch(baseUrl + "/list/get", { headers: { Accept: "application/json", Authorization: "Bearer " + getToken, }, // ❌ 移除 onResponse —— 它是副作用钩子,不参与 Promise 流程 // ✅ 改用 execute() + await data 实现可控等待 }); try { await execute(); // ✅ 触发请求并等待完成 if (error.value) throw error.value; widgets.value = data.value || []; fetchedWidgets.value = true; return widgets.value; // ✅ 明确返回处理后的数据 } catch (err) { throw showError({ statusCode: 401, statusMessage: "Error: - " + (err?.message || "Unknown error"), fatal: true, }); } } return { widgets, // ✅ 直接返回 ref,不在此处解构 loadWidgets, fetchedWidgets, // ✅ 同上 }; });
✅ 修复后的组件逻辑(<script setup>)</script>
<script setup> import { ref, onMounted } from "vue"; import { useDashboardStore } from "~/stores/dashboard"; import { useCandidatesStore } from "~/stores/candidates"; const props = defineProps({ page: { type: String, required: true, }, }); const dashboardStore = useDashboardStore(); const candidatesStore = useCandidatesStore(); // ✅ 使用 ref 存储 layout/index(确保响应式) const layout = ref([]); const index = ref(0); onMounted(async () => { try { const values = await getWidgets(); // ✅ 直接 await,无需 .then() mapWidgetsData(values); } catch (err) { console.error("Failed to load widgets:", err); } }); const getWidgets = async () => { switch (props.page) { case "dashboard": { // ✅ 关键:不使用解构!直接通过 store 实例访问 ref if (!dashboardStore.fetchedWidgets.value) { return await dashboardStore.loadWidgets(); // ✅ await 返回的数据 } return dashboardStore.widgets.value; // ✅ 访问 .value 获取当前值 } case "employees": { // 同理,使用 candidatesStore.widgets / .fetchedWidgets / .loadWidgets() const { loadWidgets, widgets, fetchedWidgets } = candidatesStore; // ⚠️ 注意:此处若仍解构,同样会丢失响应性!应改为: // if (!candidatesStore.fetchedWidgets.value) { ... } // return candidatesStore.widgets.value; break; } default: return []; } }; const mapWidgetsData = (values) => { if (!values || !Array.isArray(values.data)) return; const itemsData = values.data; layout.value = []; itemsData.forEach((value) => { try { const position = JSON.parse(value.position); layout.value.push({ ...position, i: value.id, type: value.type, }); } catch (e) { console.warn("Invalid position JSON for widget", value.id, e); } }); index.value = values.last_index + 1; }; </script>
? 关键要点总结
- 禁止解构 ref:const { widgets } = store 会剥离响应性,必须始终通过 store.widgets.value 访问;
- useFetch 不是“即用型” Promise:它返回的是一个包含 execute() 方法的对象,需显式调用 await execute() 并检查 data.value;
- onResponse 是副作用钩子,非控制流节点:它在响应到达时同步执行,但不会影响外部 Promise 的 resolve 时间点,故不适合作为 await 的依据;
- 错误处理需统一:建议在 store 层捕获并抛出业务错误,在组件层用 try/catch 处理,避免静默失败;
- Nuxt 3 兼容性提示:useFetch 在服务端渲染(SSR)中默认惰性执行,如需首屏数据,请结合 useAsyncData 或 useFetch(…, { immediate: true })。
遵循以上模式,即可确保异步加载、状态更新与数据消费形成可靠的响应式链条,彻底规避 undefined 和竞态问题。