跳转到内容

React 到 tRPC

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

在使用此生成器之前,请确保您的 React 应用程序具有:

  1. 一个渲染应用程序的 main.tsx 文件
  2. 一个 <App/> JSX 元素,tRPC 提供程序将自动注入到该元素中
  3. 一个可工作的 tRPC API(使用 tRPC API 生成器生成)
  4. 如果连接使用 Cognito 或 IAM 身份验证的 API,需要通过 ts#website#auth 生成器添加 Cognito Auth
所需 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>,
);
Terminal window
pnpm nx g @aws/nx-plugin:connection
您还可以执行试运行以查看哪些文件会被更改
Terminal window
pnpm nx g @aws/nx-plugin:connection --dry-run
参数类型默认值描述
sourceProject 必需string-源项目
targetProject 必需string-要连接到的目标项目
sourceComponent string-要从其连接的源组件(组件名称、相对于源项目根目录的路径或生成器 ID)。使用 '.' 显式选择项目作为源。
targetComponent string-要连接到的目标组件(组件名称、相对于目标项目根目录的路径或生成器 ID)。使用 '.' 显式选择项目作为目标。
preferInstallDependencies booleantrue是否在生成器运行后优先安装依赖项。设置为 false 可在批量运行多个生成器时延迟安装(如果后续生成器需要计算 Nx 项目图,仍会运行安装);在最后统一安装一次。

生成器在您的 React 应用程序中创建以下结构:

  • 文件夹src
    • 文件夹components
      • <ApiName>ClientProvider.tsx 设置 tRPC 客户端和到后端架构的绑定。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();
// Example query
const { data, isLoading, error } = useQuery(trpc.users.list.queryOptions());
// Example mutation
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>Error occurred:</h2>
<p>{error.message}</p>
{error.data?.code && <p>Code: {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 路由常规查询/变更。这意味着订阅无需额外配置即可开箱即用。

有关如何在后端中定义订阅过程的信息,请参阅 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);
},
},
);
// Clean up the subscription on unmount
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) => {
// Cancel outgoing fetches
await queryClient.cancelQueries(trpc.users.list.queryFilter());
// Get snapshot of current data
const previousUsers = queryClient.getQueryData(
trpc.users.list.queryKey(),
);
// Optimistically remove the user
queryClient.setQueryData(trpc.users.list.queryKey(), (old) =>
old?.filter((user) => user.id !== userId),
);
return { previousUsers };
},
onError: (err, userId, context) => {
// Restore previous data on error
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)}>Delete</button>
</li>
))}
</ul>
);
}

预取数据以获得更好的性能:

function UserList() {
const trpc = useMyApi();
const users = useQuery(trpc.users.list.queryOptions());
const queryClient = useQueryClient();
// Prefetch user details on hover
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 ? 'Loading...' : 'Load More'}
</button>
)}
</div>
);
}

需要注意的是,无限查询只能用于具有名为 cursor 的输入属性的过程。

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

function UserForm() {
const trpc = useMyApi();
// ✅ Input is fully typed
const createUser = trpc.users.create.useMutation();
const handleSubmit = (data: CreateUserInput) => {
// ✅ Type error if input doesn't match schema
createUser.mutate(data);
};
return <form onSubmit={handleSubmit}>{/* ... */}</form>;
}

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

如果您的 tRPC API 使用 Custom 身份验证(Lambda Authorizer),生成的客户端提供程序包含占位符 headers,您必须在其中添加授权器所需的授权标头。在生成的 <ApiName>ClientProvider.tsx 中查找 // TODO: Add headers required by your custom authorizer 注释,并将其替换为您的令牌或 API 密钥逻辑。

有关更多信息,请参阅: