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 | - | 源项目 |
| targetProject 必需 | string | - | 要连接到的目标项目 |
| sourceComponent | string | - | 要从其连接的源组件(组件名称、相对于源项目根目录的路径或生成器 ID)。使用 '.' 显式选择项目作为源。 |
| targetComponent | string | - | 要连接到的目标组件(组件名称、相对于目标项目根目录的路径或生成器 ID)。使用 '.' 显式选择项目作为目标。 |
生成器会在 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 的任何变更都能立即反映在前端代码中,无需重新构建。
更多信息请参考:
Custom Auth
Section titled “Custom Auth”If your tRPC API uses Custom authentication (Lambda Authorizer), the generated client provider includes placeholder headers where you must add the authorization headers your authorizer expects. Look for the // TODO: Add headers required by your custom authorizer comments in the generated <ApiName>ClientProvider.tsx and replace them with your token or API key logic.