跳到主要内容

useSuspense()

High performance async data rendering without overfetching.

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

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

用法​

import { useSuspense } from '@data-client/react';
import { ProfileResource } from './ProfileResource';

function ProfileDetail() {
  const profile = useSuspense(ProfileResource.get, { id: 1 });
  return (
    <div className="listItem">
      <Avatar src={profile.avatar} />
      <div>
        <h4>{profile.fullName}</h4>
        <p>{profile.bio}</p>
      </div>
    </div>
  );
}
render(<ProfileDetail />);
结果
Store▶

行为​

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

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

使用 React Navigation 时,如果数据被视为过时,useSuspense() 会在获得焦点时触发获取。

条件依赖

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

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

类型​

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

示例​

列表​

import { useSuspense } from '@data-client/react';
import { ProfileResource } from './ProfileResource';

function ProfileList() {
  const profiles = useSuspense(ProfileResource.getList);
  return (
    <div>
      {profiles.map(profile => (
        <div className="listItem" key={profile.pk()}>
          <Avatar src={profile.avatar} />
          <div>
            <h4>{profile.fullName}</h4>
            <p>{profile.bio}</p>
          </div>
        </div>
      ))}
    </div>
  );
}
render(<ProfileList />);
结果
Store▶

分页​

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

import { useSuspense } from '@data-client/react';
import PostItem from './PostItem';
import LoadMore from './LoadMore';
import { PostResource } from './Post';

export default function PostList() {
  const { posts, cursor } = useSuspense(PostResource.getList);
  return (
    <div>
      {posts.map(post => (
        <PostItem key={post.pk()} post={post} />
      ))}
      {cursor ? <LoadMore cursor={cursor} /> : null}
    </div>
  );
}
render(<PostList />);
结果
Store▶

顺序请求​

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

import { useSuspense } from '@data-client/react';
import { PostResource, UserResource } from './resources';

function PostWithAuthor({ id }: { id: string }) {
const post = useSuspense(PostResource.get, { id });
const author = useSuspense(UserResource.get, {
id: post.userId,
});
}

条件请求​

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

▶Resources
▶PostWithAuthor
import { useSuspense } from '@data-client/react';
import { PostResource, UserResource } from './Resources';

export default function PostWithAuthor({ id }: { id: string }) {
  const post = useSuspense(PostResource.get, { id });
  const author = useSuspense(
    UserResource.get,
    post.userId
      ? {
          id: post.userId,
        }
      : null,
  );
  // author as User | undefined
  if (!author) return;
}

嵌入数据​

当 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
import { useSuspense } from '@data-client/react';
import { getPosts } from './api/Post';

export default function ArticleList({ page }: { page: string }) {
  const {
    posts,
    nextPage,
    lastPage,
  } = useSuspense(getPosts, { page });
  return (
    <div>
      {posts.map(post => (
        <div key={post.pk()}>{post.title}</div>
      ))}
    </div>
  );
}

服务端渲染​

服务端渲染可以增量地流式传输 HTML,大幅降低 TTFB。Reactive Data Client SSR 会自动完成 store 注水,这意味着用户可以立即交互,且首次加载时客户端获取请求为零。

更多演示

组件中的用法完全相同,这意味着你可以轻松地在 SSR 和非 SSR 应用之间共享组件,也可以迁移到 SSR 而无需修改 data-client 相关代码。

并发模式​

在 React 18 中,使用 startTransition 进行导航,可以让 AsyncBoundary 在新数据加载期间继续显示之前的画面。结合流式服务端渲染,就不再需要闪现恼人的加载指示器,从而改善用户体验。

点击其中一个名字,即可导航到该用户的待办事项。这里较长的加载状态由干扰更少的加载条 来提示,就像 YouTube 和 Robinhood 所做的那样。

更多演示

如果你需要在自己的自定义路由中加入这一功能,请查看 React 官方指南