跳到主要内容

渲染异步数据

只需一行 useSuspense(),就能在使用数据的地方绑定数据,让你的组件可复用;它能 像 await 一样保证数据的存在。

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

export default function PostList({ setRoute }) {
  const posts = useSuspense(PostResource.getList);
  return (
    <div>
      {posts.map(post => (
        <PostItem key={post.pk()} post={post} setRoute={setRoute} />
      ))}
    </div>
  );
}
结果
Store▶
在许多上下文中使用的 Endpoint在许多上下文中使用的 Endpoint

不要进行 prop 逐层传递。而是在渲染数据的组件中直接调用 useSuspense()。这被称为 数据就近放置(data co-location)。

不要把数据绑定 hook 藏在自定义 hook 中。而应把紧密耦合的数据转换放在 Query 中——数据逻辑应当与数据模型放在一起,这样它保持可见、可复用,并且可以独立于视图自由修改。

Reactive Data Client 会在数据变化时立即自动更新绑定的组件,无需编写复杂的更新函数或级联失效逻辑。这被称为 响应式编程。

加载与错误​

你可能已经注意到,返回类型表明这个值总是存在的。useSuspense() 的工作方式非常像 await。这让我们可以把错误和加载的处理与数据的使用分离开来。

异步边界​

我们会在**页面、路由或模态框**等导航边界处或其上层放置 <AsyncBoundary />,用来处理加载和错误状态。

Dashboard.tsx
import { AsyncBoundary } from '@data-client/react';
import { Outlet } from 'react-router';

export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<section>
<AsyncBoundary>
<Outlet />
</AsyncBoundary>
</section>
</div>
);
}

借助 React 18 的 useTransition 和基于服务端渲染的路由或导航,你将再也看不到加载 fallback。在 React 16 和 17 中,可以集中管理 fallback,在保持组件可复用的同时消除冗余的加载指示器。

<AsyncBoundary /> 还能让服务端渲染以增量方式流式传输 HTML,大幅降低 TTFB。Reactive Data Client SSR 的自动 store 注水意味着首次加载时用户可以立即交互,且客户端获取次数为零。

AsyncBoundary 的错误 fallback 和加载 fallback 都可以定制。

有状态的方式​

在某些情况下,你可能会发现使用有状态的方式处理 fallback 仍然有用(在使用 React 16 和 17 时)。对于这些情况,或者为了兼容某些组件库,我们提供了 useDLE() - [D]ata [L]oading [E]rror。

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

function ProfileList(): React.JSX.Element {
  const { data, loading, error } = useDLE(ProfileResource.getList);
  if (error) return <div>Error {`${error.status}`}</div>;
  if (loading || !data) return <Loading />;
  return (
    <div>
      {data.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▶

由于 useDLE 不使用 useSuspense,你将无法轻松地集中编排加载和错误处理的 代码。此外,React 18 的特性,例如 useTransition 和增量流式 SSR,在使用它的组件中将无法工作。

条件获取​

条件依赖

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

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

订阅​

当数据可能因外部因素而发生变化时,useSubscription() 可以确保组件挂载期间持续更新。useLive() 会同时调用 useSubscription() 和 useSuspense(),让你能够非常轻松地使用最新数据。

import { useLive } from '@data-client/react';
import NumberFlow from '@number-flow/react';
import { getTicker } from './Ticker';

function AssetPrice({ productId }: Props) {
  const ticker = useLive(getTicker, { productId });
  return (
    <center>
      {productId}{' '}
      <NumberFlow
        value={ticker.price}
        format={{ style: 'currency', currency: 'USD' }}
      />
    </center>
  );
}
interface Props {
  productId: string;
}
render(<AssetPrice productId="BTC-USD" />);
结果
Store▶

订阅由 Managers 编排。开箱即用地,只需在 Endpoint 或 Resource 上添加 pollFrequency,就可以使用基于轮询的订阅。对于 SSE 和 websockets 这类基于推送的网络协议,请参阅数据流 manager 示例。

export const getTicker = new RestEndpoint({
urlPrefix: 'https://api.exchange.coinbase.com',
path: '/products/:productId/ticker',
schema: Ticker,
pollFrequency: 2000,
});