跳转到内容

React 连接到 tRPC

Nx 的 AWS 插件提供了一个生成器,可快速将您的 tRPC API 与 React 网站集成。它会设置连接 tRPC 后端所需的所有配置,包括 AWS IAM 和 Cognito 身份验证支持以及适当的错误处理。该集成在前端和 tRPC 后端之间提供完整的端到端类型安全。

使用此生成器前,请确保您的 React 应用具备:

  1. 渲染应用的 main.tsx 文件
  2. 自动注入 tRPC provider 的 <App/> JSX 元素
  3. 可运行的 tRPC API(使用 tRPC API 生成器生成)
  4. 如果连接使用 Cognito 或 IAM 认证的 API,需通过 ts#react-website-auth 生成器 添加 Cognito 认证
所需 main.tsx 结构示例
import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom/client';
import App from './app/app';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement,
);
root.render(
<StrictMode>
<App />
</StrictMode>,
);
  1. 安装 Nx Console VSCode Plugin 如果您尚未安装
  2. 在VSCode中打开Nx控制台
  3. 点击 Generate (UI) 在"Common Nx Commands"部分
  4. 搜索 @aws/nx-plugin - connection
  5. 填写必需参数
    • 点击 Generate
    参数 类型 默认值 描述
    sourceProject 必需 string - The source project
    targetProject 必需 string - The target project to connect to

    生成器会在 React 应用中创建以下结构:

    • 文件夹src
      • 文件夹components
        • <ApiName>ClientProvider.tsx 配置 tRPC 客户端并绑定到后端 schema。ApiName 将解析为 API 名称
        • QueryClientProvider.tsx TanStack React Query 客户端提供程序
      • 文件夹hooks
        • useSigV4.tsx 用于使用 SigV4 签名 HTTP 请求的钩子(仅限 IAM)
        • use<ApiName>.tsx 返回 tRPC 选项代理的钩子,用于 TanStack Query 集成
        • use<ApiName>Client.tsx 返回原生 tRPC 客户端的钩子,用于直接 API 调用

    同时会安装以下依赖:

    • @trpc/client
    • @trpc/tanstack-react-query
    • @tanstack/react-query
    • aws4fetch(如果使用 IAM 认证)
    • event-source-polyfill(如果使用 REST API,用于订阅支持)

    生成器提供 use<ApiName> 钩子,返回 tRPC 选项代理,用于 TanStack Query 钩子(如 useQueryuseMutation):

    import { useQuery, useMutation } from '@tanstack/react-query';
    import { useMyApi } from './hooks/useMyApi';
    function MyComponent() {
    const trpc = useMyApi();
    // 查询示例
    const { data, isLoading, error } = useQuery(trpc.users.list.queryOptions());
    // 变更示例
    const mutation = useMutation(trpc.users.create.mutationOptions());
    const handleCreate = () => {
    mutation.mutate({
    name: 'John Doe',
    email: 'john@example.com',
    });
    };
    if (isLoading) return <div>Loading...</div>;
    return (
    <ul>
    {data.map((user) => (
    <li key={user.id}>{user.name}</li>
    ))}
    </ul>
    );
    }

    use<ApiName>Client 钩子提供对 原生 tRPC 客户端 的访问,适用于命令式 API 调用和订阅:

    import { useState } from 'react';
    import { useMyApiClient } from './hooks/useMyApi';
    function MyComponent() {
    const client = useMyApiClient();
    const handleClick = async () => {
    const result = await client.echo.query({ message: 'Hello!' });
    console.log(result);
    const mutationResult = await client.users.create.mutate({ name: 'Jane' });
    console.log(mutationResult);
    };
    return <button onClick={handleClick}>Call API</button>;
    }

    集成包含内置错误处理机制,可正确处理 tRPC 错误:

    function MyComponent() {
    const trpc = useMyApi();
    const { data, error } = useQuery(trpc.users.list.queryOptions());
    if (error) {
    return (
    <div>
    <h2>发生错误:</h2>
    <p>{error.message}</p>
    {error.data?.code && <p>错误码:{error.data.code}</p>}
    </div>
    );
    }
    return (
    <ul>
    {data.map((user) => (
    <li key={user.id}>{user.name}</li>
    ))}
    </ul>
    );
    }

    连接到 REST API tRPC 后端时,生成的客户端会自动配置 splitLink,将订阅操作通过 httpSubscriptionLink(使用 SSE)路由,常规查询/变更通过 httpLink 路由。这意味着订阅开箱即用,无需额外配置。

    有关如何在后端定义订阅过程的信息,请参阅 ts#trpc-api 生成器指南

    您可以使用 useSubscription 钩子和选项代理中的 subscriptionOptions 来消费订阅:

    import { useSubscription } from '@trpc/tanstack-react-query';
    import { useMyApi } from './hooks/useMyApi';
    function StreamingComponent() {
    const trpc = useMyApi();
    const subscription = useSubscription(
    trpc.myStream.subscriptionOptions(
    { query: 'hello' },
    {
    enabled: true,
    onStarted: () => {
    console.log('Subscription started');
    },
    onData: (data) => {
    console.log('Received:', data.text);
    },
    onError: (error) => {
    console.error('Subscription error:', error);
    },
    },
    ),
    );
    return (
    <div>
    <p>Status: {subscription.status}</p>
    {subscription.data && <p>Latest: {subscription.data.text}</p>}
    {subscription.error && <p>Error: {subscription.error.message}</p>}
    <button onClick={() => subscription.reset()}>Reset</button>
    </div>
    );
    }

    subscription 对象提供:

    • subscription.data — 最近接收到的数据
    • subscription.error — 最近接收到的错误
    • subscription.status'idle''connecting''pending''error' 之一
    • subscription.reset() — 重置订阅(用于从错误中恢复)

    或者,您可以通过 use<ApiName>Client 钩子使用原生 tRPC 客户端,以更好地控制订阅生命周期:

    import { useState, useEffect } from 'react';
    import { useMyApiClient } from './hooks/useMyApi';
    function StreamingComponent() {
    const client = useMyApiClient();
    const [messages, setMessages] = useState<string[]>([]);
    useEffect(() => {
    const subscription = client.myStream.subscribe(
    { query: 'hello' },
    {
    onData: (data) => {
    setMessages((prev) => [...prev, data.text]);
    },
    onComplete: () => {
    console.log('Stream complete');
    },
    onError: (error) => {
    console.error('Stream error:', error);
    },
    },
    );
    // 组件卸载时清理订阅
    return () => subscription.unsubscribe();
    }, [client]);
    return (
    <ul>
    {messages.map((msg, i) => (
    <li key={i}>{msg}</li>
    ))}
    </ul>
    );
    }

    始终处理加载和错误状态以提升用户体验:

    function UserList() {
    const trpc = useMyApi();
    const users = useQuery(trpc.users.list.queryOptions());
    if (users.isLoading) {
    return <LoadingSpinner />;
    }
    if (users.error) {
    return <ErrorMessage error={users.error} />;
    }
    return (
    <ul>
    {users.data.map((user) => (
    <li key={user.id}>{user.name}</li>
    ))}
    </ul>
    );
    }

    使用乐观更新提升用户体验:

    import { useQueryClient, useQuery, useMutation } from '@tanstack/react-query';
    function UserList() {
    const trpc = useMyApi();
    const users = useQuery(trpc.users.list.queryOptions());
    const queryClient = useQueryClient();
    const deleteMutation = useMutation(
    trpc.users.delete.mutationOptions({
    onMutate: async (userId) => {
    // 取消进行中的请求
    await queryClient.cancelQueries(trpc.users.list.queryFilter());
    // 获取当前数据快照
    const previousUsers = queryClient.getQueryData(
    trpc.users.list.queryKey(),
    );
    // 乐观删除用户
    queryClient.setQueryData(trpc.users.list.queryKey(), (old) =>
    old?.filter((user) => user.id !== userId),
    );
    return { previousUsers };
    },
    onError: (err, userId, context) => {
    // 出错时恢复数据
    queryClient.setQueryData(
    trpc.users.list.queryKey(),
    context?.previousUsers,
    );
    },
    }),
    );
    return (
    <ul>
    {users.map((user) => (
    <li key={user.id}>
    {user.name}
    <button onClick={() => deleteMutation.mutate(user.id)}>删除</button>
    </li>
    ))}
    </ul>
    );
    }

    预取数据以提升性能:

    function UserList() {
    const trpc = useMyApi();
    const users = useQuery(trpc.users.list.queryOptions());
    const queryClient = useQueryClient();
    // 悬停时预取用户详情
    const prefetchUser = async (userId: string) => {
    await queryClient.prefetchQuery(trpc.users.getById.queryOptions(userId));
    };
    return (
    <ul>
    {users.map((user) => (
    <li key={user.id} onMouseEnter={() => prefetchUser(user.id)}>
    <Link to={`/users/${user.id}`}>{user.name}</Link>
    </li>
    ))}
    </ul>
    );
    }

    使用无限查询处理分页:

    function UserList() {
    const trpc = useMyApi();
    const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
    useInfiniteQuery(
    trpc.users.list.infiniteQueryOptions(
    { limit: 10 },
    {
    getNextPageParam: (lastPage) => lastPage.nextCursor,
    },
    ),
    );
    return (
    <div>
    {data?.pages.map((page) =>
    page.users.map((user) => <UserCard key={user.id} user={user} />),
    )}
    {hasNextPage && (
    <button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
    {isFetchingNextPage ? '加载中...' : '加载更多'}
    </button>
    )}
    </div>
    );
    }

    需要注意的是,无限查询只能用于输入参数包含 cursor 属性的过程。

    该集成提供完整的端到端类型安全。您的 IDE 将为所有 API 调用提供自动补全和类型检查:

    function UserForm() {
    const trpc = useMyApi();
    // ✅ 输入完全类型化
    const createUser = trpc.users.create.useMutation();
    const handleSubmit = (data: CreateUserInput) => {
    // ✅ 输入不符合 schema 时会产生类型错误
    createUser.mutate(data);
    };
    return <form onSubmit={handleSubmit}>{/* ... */}</form>;
    }

    类型会自动从后端路由器和 schema 定义中推断,确保 API 的任何变更都能立即反映在前端代码中,无需重新构建。

    更多信息请参考: