跳到主要内容

useSuspense()

High performance async data rendering without overfetching.

在 Vue 组件中 await useSuspense()。这意味着组件的其余部分只会在数据加载完成后才运行,从而免去了处理加载和错误状态的复杂性。fallback 处理则通过 Vue 内置的 Suspense 集中管理。

useSuspense() 会响应数据变更,并且只在必要时重新渲染。

用法​

<script setup lang="ts">
  import { useSuspense } from '@data-client/vue';
  import { ProfileResource } from './ProfileResource';

  const profile = await useSuspense(ProfileResource.get, { id: 1 });
</script>

<template>
  <div class="listItem">
    <Avatar :src="profile.avatar" />
    <div>
      <h4>{{ profile.fullName }}</h4>
      <p>{{ profile.bio }}</p>
    </div>
  </div>
</template>

行为​

缓存策略默认为 Stale-While-Revalidate,但也可以配置。

过期状态获取挂起错误条件
无效是1是否不在 store 中、删除、失效、 invalidIfStale
过时是1否否(首次渲染、参数变化)且 expiry < now
有效否否可能2获取完成
否否否第二个参数传入 null
备注
  1. 相同的获取请求会自动去重
  2. 硬错误会被 onErrorCaptured() 捕获
条件依赖

将 null 作为任意 Data Client hook 的第二个参数,表示“什么都不做”。

// todo could be undefined if id is undefined
const todo = await useSuspense(
TodoResource.get,
computed(() => (id.value ? { id: id.value } : null)),
);

类型​

function useSuspense(
endpoint: ReadEndpoint,
...args: MaybeRefsOrGetters<Parameters<typeof endpoint>> | [null]
): Promise<DeepReadonly<ComputedRef<Denormalize<typeof endpoint.schema>>>>;

参数可以是普通值、refs(包括 computed),或者像 () => ({ id: props.id }) 这样的 getter 函数。像 { id: props.id } 这样的普通对象只会被读取一次,不会跟随 prop 或路由的变化,因此当参数可能变化时,请使用 getter 或 computed。

参数变化时,结果会随之更新。在新参数对应的数据加载期间,结果会保留之前的数据,而不会变成 undefined。如果这次获取失败,读取结果时会抛出该错误(取决于其错误策略),从而让错误到达 onErrorCaptured()。

示例​

列表​

<script setup lang="ts">
  import { useSuspense } from '@data-client/vue';
  import { ProfileResource } from './ProfileResource';

  const profiles = await useSuspense(ProfileResource.getList);
</script>

<template>
  <div>
    <div class="listItem" v-for="profile in profiles" :key="profile.pk()">
      <Avatar :src="profile.avatar" />
      <div>
        <h4>{{ profile.fullName }}</h4>
        <p>{{ profile.bio }}</p>
      </div>
    </div>
  </div>
</template>

分页​

响应式分页通过可变 schema 实现

<script setup lang="ts">
  import { useSuspense } from '@data-client/vue';
  import PostItem from './PostItem.vue';
  import LoadMore from './LoadMore.vue';
  import { PostResource } from './Post';

  const data = await useSuspense(PostResource.getList);
</script>

<template>
  <div>
    <PostItem v-for="post in data.posts" :key="post.pk()" :post="post" />
    <LoadMore v-if="data.cursor" :cursor="data.cursor" />
  </div>
</template>

顺序请求​

当获取参数依赖于另一个资源的数据时。

<script setup lang="ts">
import { computed } from 'vue';
import { useSuspense } from '@data-client/vue';
import { PostResource, UserResource } from './Resources';

const props = defineProps<{ id: string }>();
const post = await useSuspense(PostResource.get, () => ({ id: props.id }));
const author = await useSuspense(UserResource.get, () => ({
id: post.value.userId,
}));
</script>

条件请求​

传入 null 可以避免绑定和获取数据

▶Resources
▶PostWithAuthor.vue
<script setup lang="ts">
  import { computed } from 'vue';
  import { useSuspense } from '@data-client/vue';
  import { PostResource, UserResource } from './Resources';

  const props = defineProps<{ id: string }>();
  const post = await useSuspense(PostResource.get, () => ({ id: props.id }));
  const author = await useSuspense(
    UserResource.get,
    computed(() =>
      post.value.userId
        ? {
            id: post.value.userId,
          }
        : null,
    ),
  );
  // author as ComputedRef<User | undefined>
</script>

<template>
  <div v-if="author">
    <!-- render author -->
  </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: {
    posts: new Collection([PaginatedPost]),
    nextPage: '',
    lastPage: '',
  },
});
▶ArticleList.vue
<script setup lang="ts">
  import { useSuspense } from '@data-client/vue';
  import { getPosts } from './api/Post';

  const props = defineProps<{ page: string }>();
  const data = await useSuspense(getPosts, () => ({ page: props.page }));
</script>

<template>
  <div>
    <div v-for="post in data.posts" :key="post.pk()">{{ post.title }}</div>
  </div>
</template>