React 连接到 tRPC
Nx 的 AWS 插件提供了一个生成器,可快速将您的 tRPC API 与 React 网站集成。它会设置连接 tRPC 后端所需的所有配置,包括 AWS IAM 和 Cognito 身份验证支持以及适当的错误处理。该集成在前端和 tRPC 后端之间提供完整的端到端类型安全。
使用此生成器前,请确保您的 React 应用具备:
- 渲染应用的
main.tsx文件 - 自动注入 tRPC provider 的
<App/>JSX 元素 - 可运行的 tRPC API(使用 tRPC API 生成器生成)
- 如果连接使用 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>,);- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - connection - 填写必需参数
- 点击
Generate
pnpm nx g @aws/nx-plugin:connectionyarn nx g @aws/nx-plugin:connectionnpx nx g @aws/nx-plugin:connectionbunx nx g @aws/nx-plugin:connection| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| 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-queryaws4fetch(如果使用 IAM 认证)event-source-polyfill(如果使用 REST API,用于订阅支持)
使用生成代码
Section titled “使用生成代码”使用 tRPC 选项代理钩子
Section titled “使用 tRPC 选项代理钩子”生成器提供 use<ApiName> 钩子,返回 tRPC 选项代理,用于 TanStack Query 钩子(如 useQuery 和 useMutation):
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> );}使用原生 tRPC 客户端
Section titled “使用原生 tRPC 客户端”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> );}订阅(流式传输)
Section titled “订阅(流式传输)”连接到 REST API tRPC 后端时,生成的客户端会自动配置 splitLink,将订阅操作通过 httpSubscriptionLink(使用 SSE)路由,常规查询/变更通过 httpLink 路由。这意味着订阅开箱即用,无需额外配置。
有关如何在后端定义订阅过程的信息,请参阅 ts#trpc-api 生成器指南。
使用 useSubscription 钩子
Section titled “使用 useSubscription 钩子”您可以使用 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()— 重置订阅(用于从错误中恢复)
使用原生客户端
Section titled “使用原生客户端”或者,您可以通过 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> );}处理加载状态
Section titled “处理加载状态”始终处理加载和错误状态以提升用户体验:
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 的任何变更都能立即反映在前端代码中,无需重新构建。
更多信息请参考: