数据获取(useData)
ubean 提供 useData() 用于声明式数据获取,支持缓存、TTL 与失效。它从 ubean/client 自动导入(位于 UBEAN_CLIENT_PRESET 中)。
ubean 不提供
defineLoader。要在服务端或客户端获取数据,请在<script setup>里使用useData()。
基本用法
useData 接收单个 options 对象 —— key(稳定的字符串)加上 fetcher(加载数据的异步函数)。它在 <script setup> 中被直接 await:
<script setup lang="ts">
// useData 是自动导入的
const { data, error, loading, refresh, invalidate } = await useData({
key: 'posts',
fetcher: () => fetch('/api/posts').then(r => r.json())
});
</script>
<template>
<div v-if="loading">Loading…</div>
<div v-else-if="error">Error: {{ error.message }}</div>
<ul v-else>
<li v-for="post in data.posts" :key="post.id">{{ post.title }}</li>
</ul>
</template>异步 fetcher
<script setup lang="ts">
const { data } = await useData({
key: 'user',
fetcher: async () => {
const res = await fetch('/api/user');
return res.json();
}
});
</script>读取路由参数
在 fetcher 内读取路由参数请用 useRouter()(从 vue-router 导入,或启用 autoImports: { vueRouter: true }):
<script setup lang="ts">
const router = useRouter();
const { data: post } = await useData({
key: `post-${router.currentRoute.value.params.id}`,
fetcher: () => fetch(`/api/posts/${router.currentRoute.value.params.id}`).then(r => r.json())
});
</script>多个数据源
组合多个 useData 调用:
<script setup lang="ts">
const { data: posts } = await useData({
key: 'posts',
fetcher: () => fetch('/api/posts').then(r => r.json())
});
const { data: user } = await useData({
key: 'user',
fetcher: () => fetch('/api/user').then(r => r.json())
});
</script>依赖型数据
缓存键是静态字符串 —— useData 每次挂载对同一个 key 只获取一次。要在依赖变化时重新获取,请调用 refresh()(例如在 watcher 里):
<script setup lang="ts">
import { computed, watch } from 'vue';
const router = useRouter();
const postId = computed(() => router.currentRoute.value.params.id as string);
const { data: post, refresh } = await useData({
key: `post-${postId.value}`,
fetcher: () => fetch(`/api/posts/${postId.value}`).then(r => r.json())
});
// 路由参数变化时重新获取
watch(postId, () => refresh());
</script>缓存与 TTL
<script setup lang="ts">
const { data } = await useData({
key: 'config',
fetcher: () => fetch('/api/config').then(r => r.json()),
ttl: 60_000, // 缓存 60 秒
tags: ['config'], // 用于按组失效的标签(invalidateData('config'))
dedupe: true // 合并同 key 的进行中请求(默认开启)
});
</script>刷新与失效
<script setup lang="ts">
const { data, refresh, invalidate } = await useData({
key: 'posts',
fetcher: fetchPosts
});
async function handleRefresh() {
await refresh();
}
// 使该 key 失效(下次访问时重新获取)
function handleInvalidate() {
invalidate();
}
</script>错误处理
<script setup lang="ts">
const { data, error } = await useData({
key: 'posts',
fetcher: async () => {
const res = await fetch('/api/posts');
if (!res.ok) {
throw new Error(`Failed: ${res.status}`);
}
return res.json();
}
});
</script>
<template>
<div v-if="error" class="error">{{ error.message }}</div>
</template>仅客户端获取
对于只应在客户端加载的数据(水合之后),使用 Vue 的 onMounted:
<script setup lang="ts">
import { ref, onMounted } from 'vue';
const data = ref(null);
onMounted(async () => {
const res = await fetch('/api/analytics');
data.value = await res.json();
});
</script>API 路由
服务端数据推荐用 defineHandler 写 API 路由:
// src/routes/api/posts.ts
import { defineHandler } from 'ubean/server';
export const GET = defineHandler(async c => {
const posts = await fetchPostsFromDB();
return c.json({ posts });
});最佳实践
- 稳定的 key:使用稳定的字符串 key,才能跨组件共享缓存
- 处理错误:始终在模板中处理
error状态 - 设置 TTL:为不常变更的数据设置 TTL
- 写操作后失效:写入完成后调用
invalidate()重新获取 - SSR 数据载荷:SSR 期间通过
useData获取的数据会序列化进__UBEAN_DATA__载荷,并在客户端自动水合 —— 无需额外配置
useFetch
useFetch 包装了 useAsyncData。它不自研 HTTP 客户端:请通过 setDefaultFetch(createRequest())(或 options.request)传入一个鸭子类型的 @soybeanjs/fetch 实例。未提供客户端时,它回退到 fetch + JSON。
<script setup lang="ts">
import { createRequest } from '@soybeanjs/fetch';
setDefaultFetch(createRequest());
const { data, pending, refresh } = await useFetch('posts', '/api/posts');
</script>