useDLE() - [D]ata [L]oading [E]rror
高性能的异步数据渲染,不会过度获取,并附带获取的元数据。
如果你无法使用 suspense,useDLE() 与 useSuspense() 完全一样,只是它会返回 [D]ata [L]oading [E]rror(数据、加载、错误)这几个值。
useDLE() 会响应数据变更,只在必要时重新渲染。
用法
ProfileResource
ProfileList.vue
<script setup lang="ts"> import { useDLE } from '@data-client/vue'; import { ProfileResource } from './ProfileResource'; const { data, loading, error } = useDLE(ProfileResource.getList); </script> <template> <div v-if="error">Error {{ error.status }}</div> <Loading v-else-if="loading || !data" /> <div v-else> <div class="listItem" v-for="profile in data" :key="profile.pk()"> <Avatar :src="profile.avatar" /> <div> <h4>{{ profile.fullName }}</h4> <p>{{ profile.bio }}</p> </div> </div> </div> </template>
行为
data、loading 和 error 各自都是一个 ComputedRef。请在 <script setup> 的顶层解构它们,这样它们在模板中会被自动解包。下表描述的是它们的 .value。
| 过期状态 | 获取 | Data | Loading | Error | 条件 |
|---|---|---|---|---|---|
| Invalid | 是1 | undefined | true | false | 不在 store 中、删除、失效、invalidIfStale |
| Stale | 是1 | 反规范化后的值 | false | false | (首次渲染、参数变化)且 expiry < now |
| Valid | 否 | 反规范化后的值 | false | 可能2 | 获取完成 |
| 否 | undefined | false | false | 第二个参数传入 null |
备注
- 相同的获取会被自动去重
- 硬错误会被 onErrorCaptured() 捕获
条件依赖
将 null 作为任意 Data Client hook 的第二个参数,表示“什么都不做”。
// todo could be undefined if id is undefined
const todo = useDLE(
TodoResource.get,
computed(() => (id.value ? { id: id.value } : null)),
);
类型
function useDLE(
endpoint: ReadEndpoint,
...args: MaybeRefsOrGetters<Parameters<typeof endpoint>> | [null]
): {
data: ComputedRef<DenormalizeNullable<typeof endpoint.schema>>;
loading: ComputedRef<boolean>;
error: ComputedRef<ErrorTypes | undefined>;
};
参数可以是普通值、refs(包括 computed),或者像 () => ({ id: props.id }) 这样的 getter
函数。像 { id: props.id } 这样的普通对象只会被读取一次,不会跟随 prop 或路由的变化,因此当参数可能变化时,请使用 getter 或 computed。
参数变化时,结果会随之更新。
示例
详情
ProfileResource
ProfileDetail.vue
<script setup lang="ts"> import { useDLE } from '@data-client/vue'; import { ProfileResource } from './ProfileResource'; const { data: profile, loading, error, } = useDLE(ProfileResource.get, { id: 1 }); </script> <template> <div v-if="error">Error {{ error.status }}</div> <Loading v-else-if="loading || !profile" /> <div v-else class="listItem"> <Avatar :src="profile.avatar" /> <div> <h4>{{ profile.fullName }}</h4> <p>{{ profile.bio }}</p> </div> </div> </template>
条件获取
传入 null 可以避免绑定和获取数据
▶Resources
▶PostWithAuthor.vue
<script setup lang="ts"> import { computed } from 'vue'; import { useDLE } from '@data-client/vue'; import { PostResource, UserResource } from './Resources'; const props = defineProps<{ id: string }>(); const { data: post, loading: postLoading, error: postError, } = useDLE(PostResource.get, () => ({ id: props.id })); const { data: author, loading: authorLoading, error: authorError, } = useDLE( UserResource.get, computed(() => post.value?.userId ? { id: post.value.userId, } : null, ), ); </script> <template> <div v-if="postError">Error {{ postError.status }}</div> <Loading v-else-if="postLoading || !post" /> <div v-else-if="authorError">Error {{ authorError.status }}</div> <Loading v-else-if="authorLoading || !author" /> <div v-else>{{ author.username }}</div> </template>
嵌入数据
当 Entity 存储在嵌套结构中时,该结构会被保留。
▶api/Post
import { Entity, RestEndpoint, Collection } from '@data-client/rest'; export class PaginatedPost extends Entity { id = ''; title = ''; content = ''; static key = 'PaginatedPost'; } export const getPosts = new RestEndpoint({ path: '/post', searchParams: { page: '' }, schema: { results: new Collection([PaginatedPost]), nextPage: '', lastPage: '', }, });
▶ArticleList.vue
<script setup lang="ts"> import { useDLE } from '@data-client/vue'; import { getPosts } from './api/Post'; const props = defineProps<{ page: string }>(); const { data, loading, error } = useDLE(getPosts, () => ({ page: props.page })); </script> <template> <div v-if="error">Error {{ error.status }}</div> <Loading v-else-if="loading || !data" /> <div v-else> <div v-for="post in data.results" :key="post.pk()"> {{ post.title }} </div> </div> </template>