React 连接到 FastAPI
connection 生成器提供了一种快速将 React 网站与 FastAPI 后端集成的方式。它设置了所有必要的配置,以类型安全的方式连接到 FastAPI 后端,包括客户端和 TanStack Query 钩子生成、AWS IAM 和 Cognito 身份验证支持以及正确的错误处理。
使用此生成器前,请确保您的 React 应用具备:
- 渲染应用的
main.tsx文件 - 可运行的 FastAPI 后端(使用 FastAPI 生成器生成)
- 如果连接的 API 使用 Cognito 或 IAM 身份验证,需通过
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)。使用 '.' 显式选择项目作为目标。 |
生成器将修改 FastAPI 项目中的以下文件:
文件夹scripts
- generate_open_api.py 添加生成 API OpenAPI 规范的脚本
- project.json 在构建中添加调用上述生成脚本的新目标
生成器将修改 React 应用中的以下文件:
文件夹src
文件夹components
- <ApiName>Provider.tsx API 客户端提供者
- QueryClientProvider.tsx TanStack React Query 客户端提供者
文件夹hooks
- use<ApiName>.tsx 添加通过 TanStack Query 管理状态调用 API 的钩子
- use<ApiName>Client.tsx 添加实例化可调用 API 的普通客户端钩子
- useSigV4.tsx 添加使用 SigV4 签名 HTTP 请求的钩子(如果选择 IAM 身份验证)
- project.json 在构建中添加生成类型安全客户端的新目标
- .gitignore 默认忽略生成的客户端文件
生成器还会在网站基础设施中添加运行时配置(如果尚未存在),确保 FastAPI 的 API URL 在网站中可用,并由 use<ApiName>.tsx 钩子自动配置。
在构建时,根据 FastAPI 的 OpenAPI 规范生成类型安全客户端。这将在 React 应用中添加三个新文件:
文件夹src
文件夹generated
文件夹<ApiName>
- types.gen.ts 从 FastAPI 的 pydantic 模型生成的类型
- client.gen.ts 调用 API 的类型安全客户端
- options-proxy.gen.ts 提供创建 TanStack Query 钩子选项的方法,用于通过 TanStack Query 与 API 交互
使用生成的代码
Section titled “使用生成的代码”生成的类型安全客户端可用于从 React 应用调用 FastAPI。建议通过 TanStack Query 钩子使用客户端,但也可直接使用普通客户端。
文件监听依赖
watch-generate:<ApiName>-client 依赖 nx watch 命令,需要 Nx Daemon 运行。因此如果禁用了守护进程,修改 FastAPI 时客户端不会自动重新生成。
使用 API 钩子
Section titled “使用 API 钩子”生成器提供 use<ApiName> 钩子,可通过 TanStack Query 调用 API。
使用 queryOptions 方法获取调用 API 所需的选项,结合 TanStack Query 的 useQuery 钩子:
import { useQuery } from '@tanstack/react-query';import { useState, useEffect } from 'react';import { useMyApi } from './hooks/useMyApi';
function MyComponent() { const api = useMyApi(); const item = useQuery(api.getItem.queryOptions({ itemId: 'some-id' }));
if (item.isLoading) return <div>加载中...</div>; if (item.isError) return <div>错误:{item.error.message}</div>;
return <div>项目:{item.data.name}</div>;}直接使用 API 客户端
import { useState, useEffect } from 'react';import { useMyApiClient } from './hooks/useMyApiClient';
function MyComponent() { const api = useMyApiClient(); const [item, setItem] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null);
useEffect(() => { const fetchItem = async () => { try { const data = await api.getItem({ itemId: 'some-id' }); setItem(data); } catch (err) { setError(err); } finally { setLoading(false); } }; fetchItem(); }, [api]);
if (loading) return <div>加载中...</div>; if (error) return <div>错误:{error.message}</div>;
return <div>项目:{item.name}</div>;}生成的钩子支持使用 TanStack Query 的 useMutation 处理变更操作,提供加载状态、错误处理和乐观更新等功能。
import { useMutation } from '@tanstack/react-query';import { useMyApi } from './hooks/useMyApi';
function CreateItemForm() { const api = useMyApi(); // 使用生成的变更选项创建变更 const createItem = useMutation(api.createItem.mutationOptions());
const handleSubmit = (e) => { e.preventDefault(); createItem.mutate({ name: '新项目', description: '新项目描述' }); };
return ( <form onSubmit={handleSubmit}> {/* 表单字段 */} <button type="submit" disabled={createItem.isPending} > {createItem.isPending ? '创建中...' : '创建项目'} </button>
{createItem.isSuccess && ( <div className="success"> 创建项目 ID:{createItem.data.id} </div> )}
{createItem.isError && ( <div className="error"> 错误:{createItem.error.message} </div> )} </form> );}也可为不同变更状态添加回调:
const createItem = useMutation({ ...api.createItem.mutationOptions(), onSuccess: (data) => { // 变更成功时执行 console.log('项目已创建:', data); // 可导航至新项目 navigate(`/items/${data.id}`); }, onError: (error) => { // 变更失败时执行 console.error('创建项目失败:', error); }, onSettled: () => { // 变更完成时执行(无论成功或失败) // 适合使可能受影响的查询失效 queryClient.invalidateQueries({ queryKey: api.listItems.queryKey() }); }});直接使用客户端进行变更
import { useState } from 'react';import { useMyApiClient } from './hooks/useMyApiClient';
function CreateItemForm() { const api = useMyApiClient(); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [createdItem, setCreatedItem] = useState(null);
const handleSubmit = async (e) => { e.preventDefault(); setIsLoading(true); setError(null);
try { const newItem = await api.createItem({ name: '新项目', description: '新项目描述' }); setCreatedItem(newItem); // 可导航至新项目 // navigate(`/items/${newItem.id}`); } catch (err) { setError(err); console.error('创建项目失败:', err); } finally { setIsLoading(false); } };
return ( <form onSubmit={handleSubmit}> {/* 表单字段 */} <button type="submit" disabled={isLoading} > {isLoading ? '创建中...' : '创建项目'} </button>
{createdItem && ( <div className="success"> 创建项目 ID:{createdItem.id} </div> )}
{error && ( <div className="error"> 错误:{error.message} </div> )} </form> );}无限滚动分页
Section titled “无限滚动分页”对于接受 cursor 参数的端点,生成的钩子支持使用 TanStack Query 的 useInfiniteQuery 实现无限查询,便于实现”加载更多”或无限滚动功能。
import { useInfiniteQuery } from '@tanstack/react-query';import { useMyApi } from './hooks/useMyApi';
function ItemList() { const api = useMyApi(); const items = useInfiniteQuery({ ...api.listItems.infiniteQueryOptions({ limit: 10, // 每页项目数 }, { // 确保定义 getNextPageParam 函数返回作为下一页 'cursor' 的参数 getNextPageParam: (lastPage) => lastPage.nextCursor || undefined }), });
if (items.isLoading) { return <LoadingSpinner />; }
if (items.isError) { return <ErrorMessage message={items.error.message} />; }
return ( <div> {/* 展开页面数组渲染所有项目 */} <ul> {items.data.pages.flatMap(page => page.items.map(item => ( <li key={item.id}>{item.name}</li> )) )} </ul>
<button onClick={() => items.fetchNextPage()} disabled={!items.hasNextPage || items.isFetchingNextPage} > {items.isFetchingNextPage ? '加载更多...' : items.hasNextPage ? '加载更多' : '没有更多项目'} </button> </div> );}生成的钩子会自动处理基于游标的分页(如果 API 支持)。nextCursor 值从响应中提取并用于获取下一页。
直接使用客户端分页
import { useState, useEffect } from 'react';import { useMyApiClient } from './hooks/useMyApiClient';
function ItemList() { const api = useMyApiClient(); const [items, setItems] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [nextCursor, setNextCursor] = useState(null); const [isFetchingMore, setIsFetchingMore] = useState(false);
// 获取初始数据 useEffect(() => { const fetchItems = async () => { try { setIsLoading(true); const response = await api.listItems({ limit: 10 }); setItems(response.items); setNextCursor(response.nextCursor); } catch (err) { setError(err); } finally { setIsLoading(false); } };
fetchItems(); }, [api]);
// 加载更多项目函数 const loadMore = async () => { if (!nextCursor) return;
try { setIsFetchingMore(true); const response = await api.listItems({ limit: 10, cursor: nextCursor });
setItems(prevItems => [...prevItems, ...response.items]); setNextCursor(response.nextCursor); } catch (err) { setError(err); } finally { setIsFetchingMore(false); } };
if (isLoading) { return <LoadingSpinner />; }
if (error) { return <ErrorMessage message={error.message} />; }
return ( <div> <ul> {items.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul>
<button onClick={loadMore} disabled={!nextCursor || isFetchingMore} > {isFetchingMore ? '加载更多...' : nextCursor ? '加载更多' : '没有更多项目'} </button> </div> );}集成包含内置的错误处理,带有类型化的错误响应。生成 <operation-name>Error 类型封装 OpenAPI 规范中定义的可能错误响应。每个错误都有 status 和 error 属性,通过检查 status 可缩小到特定错误类型。
import { useMutation } from '@tanstack/react-query';
function MyComponent() { const api = useMyApi(); const createItem = useMutation(api.createItem.mutationOptions());
const handleClick = () => { createItem.mutate({ name: '新项目' }); };
if (createItem.error) { switch (createItem.error.status) { case 400: // error.error 类型为 CreateItem400Response return ( <div> <h2>无效输入:</h2> <p>{createItem.error.error.message}</p> <ul> {createItem.error.error.validationErrors.map((err) => ( <li key={err.field}>{err.message}</li> ))} </ul> </div> ); case 403: // error.error 类型为 CreateItem403Response return ( <div> <h2>未授权:</h2> <p>{createItem.error.error.reason}</p> </div> ); case 500: case 502: // error.error 类型为 CreateItem5XXResponse return ( <div> <h2>服务器错误:</h2> <p>{createItem.error.error.message}</p> <p>追踪 ID:{createItem.error.error.traceId}</p> </div> ); } }
return <button onClick={handleClick}>创建项目</button>;}直接使用客户端的错误处理
function MyComponent() { const api = useMyApiClient(); const [error, setError] = useState<CreateItemError | null>(null);
const handleClick = async () => { try { await api.createItem({ name: '新项目' }); } catch (e) { const err = e as CreateItemError; setError(err); } };
if (error) { switch (error.status) { case 400: // error.error 类型为 CreateItem400Response return ( <div> <h2>无效输入:</h2> <p>{error.error.message}</p> <ul> {error.error.validationErrors.map((err) => ( <li key={err.field}>{err.message}</li> ))} </ul> </div> ); case 403: // error.error 类型为 CreateItem403Response return ( <div> <h2>未授权:</h2> <p>{error.error.reason}</p> </div> ); case 500: case 502: // error.error 类型为 CreateItem5XXResponse return ( <div> <h2>服务器错误:</h2> <p>{error.error.message}</p> <p>追踪 ID:{error.error.traceId}</p> </div> ); } }
return <button onClick={handleClick}>创建项目</button>;}如果已 配置 FastAPI 流式响应,useQuery 钩子会在新数据块到达时自动更新数据。
例如:
function MyStreamingComponent() { const api = useMyApi(); const stream = useQuery(api.myStream.queryOptions());
return ( <ul> {(stream.data ?? []).map((chunk) => ( <li> {chunk.timestamp.toISOString()}: {chunk.message} </li> ))} </ul> );}可使用 isLoading 和 fetchStatus 属性确定流的当前状态。流生命周期如下:
-
发送启动流的 HTTP 请求
isLoading为truefetchStatus为'fetching'data为undefined
-
收到第一个数据块
isLoading变为falsefetchStatus保持'fetching'data变为包含第一个数据块的数组
-
收到后续数据块
isLoading保持falsefetchStatus保持'fetching'data在每次收到数据块时更新
-
流完成
isLoading保持falsefetchStatus变为'idle'data包含所有接收到的数据块
直接使用客户端处理流
如果已 配置 FastAPI 流式响应,生成的客户端将包含使用 for await 语法异步迭代流数据块的类型安全方法。
例如:
function MyStreamingComponent() { const api = useMyApiClient();
const [chunks, setChunks] = useState<Chunk[]>([]);
useEffect(() => { const streamChunks = async () => { for await (const chunk of api.myStream()) { setChunks((prev) => [...prev, chunk]); } }; streamChunks(); }, [api]);
return ( <ul> {chunks.map((chunk) => ( <li> {chunk.timestamp.toISOString()}: {chunk.message} </li> ))} </ul> );}自定义生成代码
Section titled “自定义生成代码”默认情况下,FastAPI 中使用 PUT、POST、PATCH 和 DELETE HTTP 方法的操作视为变更,其他视为查询。
可通过 x-query 和 x-mutation 修改此行为。
x-query
Section titled “x-query”@app.post( "/items", openapi_extra={ "x-query": True })def list_items(): # ...生成的钩子将提供 queryOptions(即使使用 POST 方法):
const items = useQuery(api.listItems.queryOptions());x-mutation
Section titled “x-mutation”@app.get( "/start-processing", openapi_extra={ "x-mutation": True })def start_processing(): # ...生成的钩子将提供 mutationOptions(即使使用 GET 方法):
// 生成的钩子将包含自定义选项const startProcessing = useMutation(api.startProcessing.mutationOptions());自定义分页游标
Section titled “自定义分页游标”默认情况下,生成的钩子假设分页游标参数名为 cursor。可通过 x-cursor 扩展自定义:
@app.get( "/items", openapi_extra={ # 指定不同的游标参数名 "x-cursor": "page_token" })def list_items(page_token: str = None, limit: int = 10): # ... return { "items": items, "page_token": next_page_token }如果不想为操作生成 infiniteQueryOptions,可将 x-cursor 设为 False:
@app.get( "/items", openapi_extra={ # 禁用此端点的游标分页 "x-cursor": False })def list_items(page: int = 1, limit: int = 10): # ... return { "items": items, "total": total_count, "page": page, "pages": total_pages }生成的钩子和客户端方法根据 FastAPI 端点的 OpenAPI 标签自动组织,便于查找相关操作。
例如:
@app.get( "/items", tags=["items"],)def list(): # ...
@app.post( "/items", tags=["items"],)def create(item: Item): # ...@app.get( "/users", tags=["users"],)def list(): # ...生成的钩子将按标签分组:
import { useQuery, useMutation } from '@tanstack/react-query';import { useMyApi } from './hooks/useMyApi';
function ItemsAndUsers() { const api = useMyApi();
// items 操作分组在 api.items 下 const items = useQuery(api.items.list.queryOptions()); const createItem = useMutation(api.items.create.mutationOptions());
// users 操作分组在 api.users 下 const users = useQuery(api.users.list.queryOptions());
// 使用示例 const handleCreateItem = () => { createItem.mutate({ name: '新项目' }); };
return ( <div> <h2>项目</h2> <ul> {items.data?.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> <button onClick={handleCreateItem}>添加项目</button>
<h2>用户</h2> <ul> {users.data?.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> </div> );}这种分组便于组织 API 调用,并在 IDE 中提供更好的代码补全。
直接使用分组客户端操作
import { useState, useEffect } from 'react';import { useMyApiClient } from './hooks/useMyApiClient';
function ItemsAndUsers() { const api = useMyApiClient(); const [items, setItems] = useState([]); const [users, setUsers] = useState([]); const [isLoading, setIsLoading] = useState(true);
// 加载数据 useEffect(() => { const fetchData = async () => { try { setIsLoading(true);
// items 操作分组在 api.items 下 const itemsData = await api.items.list(); setItems(itemsData);
// users 操作分组在 api.users 下 const usersData = await api.users.list(); setUsers(usersData); } catch (error) { console.error('获取数据错误:', error); } finally { setIsLoading(false); } };
fetchData(); }, [api]);
const handleCreateItem = async () => { try { // 使用分组方法创建项目 const newItem = await api.items.create({ name: '新项目' }); setItems(prevItems => [...prevItems, newItem]); } catch (error) { console.error('创建项目错误:', error); } };
if (isLoading) { return <div>加载中...</div>; }
return ( <div> <h2>项目</h2> <ul> {items.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> <button onClick={handleCreateItem}>添加项目</button>
<h2>用户</h2> <ul> {users.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> </div> );}可通过定义自定义异常类、异常处理程序并为不同状态码指定响应模型来自定义 FastAPI 的错误响应。生成的客户端将自动处理这些自定义错误类型。
定义自定义错误模型
Section titled “定义自定义错误模型”首先使用 Pydantic 定义错误模型:
from pydantic import BaseModel
class ErrorDetails(BaseModel): message: str
class ValidationError(BaseModel): message: str field_errors: list[str]创建自定义异常
Section titled “创建自定义异常”为不同错误场景创建异常类:
class NotFoundException(Exception): def __init__(self, message: str): self.message = message
class ValidationException(Exception): def __init__(self, details: ValidationError): self.details = details添加异常处理程序
Section titled “添加异常处理程序”注册异常处理程序将异常转换为 HTTP 响应:
from fastapi import Requestfrom fastapi.responses import JSONResponse
@app.exception_handler(NotFoundException)async def not_found_handler(request: Request, exc: NotFoundException): return JSONResponse( status_code=404, content=exc.message, )
@app.exception_handler(ValidationException)async def validation_error_handler(request: Request, exc: ValidationException): return JSONResponse( status_code=400, content=exc.details.model_dump(), )指定响应模型
Section titled “指定响应模型”在端点定义中为不同状态码指定响应模型:
@app.get( "/items/{item_id}", responses={ 404: {"model": str} 500: {"model": ErrorDetails} })def get_item(item_id: str) -> Item: item = find_item(item_id) if not item: raise NotFoundException(message=f"未找到 ID 为 {item_id} 的项目") return item
@app.post( "/items", responses={ 400: {"model": ValidationError}, 403: {"model": str} })def create_item(item: Item) -> Item: if not is_valid(item): raise ValidationException( ValidationError( message="无效项目数据", field_errors=["名称必填"] ) ) return save_item(item)在 React 中使用自定义错误类型
Section titled “在 React 中使用自定义错误类型”生成的客户端将自动处理这些自定义错误类型,允许类型检查和不同错误响应处理:
import { useMutation, useQuery } from '@tanstack/react-query';
function ItemComponent() { const api = useMyApi();
// 带类型错误处理的查询 const getItem = useQuery({ ...api.getItem.queryOptions({ itemId: '123' }), onError: (error) => { // 错误类型基于 FastAPI 的响应 switch (error.status) { case 404: // error.error 类型为字符串 console.error('未找到:', error.error); break; case 500: // error.error 类型为 ErrorDetails console.error('服务器错误:', error.error.message); break; } } });
// 带类型错误处理的变更 const createItem = useMutation({ ...api.createItem.mutationOptions(), onError: (error) => { switch (error.status) { case 400: // error.error 类型为 ValidationError console.error('验证错误:', error.error.message); console.error('字段错误:', error.error.field_errors); break; case 403: // error.error 类型为字符串 console.error('禁止访问:', error.error); break; } } });
// 带错误处理的组件渲染 if (getItem.isError) { if (getItem.error.status === 404) { return <NotFoundMessage message={getItem.error.error} />; } else { return <ErrorMessage message={getItem.error.error.message} />; } }
return ( <div> {/* 组件内容 */} </div> );}直接处理客户端自定义错误
import { useState, useEffect } from 'react';
function ItemComponent() { const api = useMyApiClient(); const [item, setItem] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true);
// 带错误处理的获取项目 useEffect(() => { const fetchItem = async () => { try { setLoading(true); const data = await api.getItem({ itemId: '123' }); setItem(data); } catch (e) { // 错误类型基于 FastAPI 的响应 const err = e as GetItemError; setError(err);
switch (err.status) { case 404: // err.error 类型为字符串 console.error('未找到:', err.error); break; case 500: // err.error 类型为 ErrorDetails console.error('服务器错误:', err.error.message); break; } } finally { setLoading(false); } };
fetchItem(); }, [api]);
// 带错误处理的创建项目 const handleCreateItem = async (data) => { try { await api.createItem(data); } catch (e) { const err = e as CreateItemError;
switch (err.status) { case 400: // err.error 类型为 ValidationError console.error('验证错误:', err.error.message); console.error('字段错误:', err.error.field_errors); break; case 403: // err.error 类型为字符串 console.error('禁止访问:', err.error); break; } } };
// 带错误处理的组件渲染 if (loading) { return <LoadingSpinner />; }
if (error) { if (error.status === 404) { return <NotFoundMessage message={error.error} />; } else if (error.status === 500) { return <ErrorMessage message={error.error.message} />; } }
return ( <div> {/* 组件内容 */} </div> );}处理加载状态
Section titled “处理加载状态”始终处理加载和错误状态以提升用户体验:
import { useQuery } from '@tanstack/react-query';
function ItemList() { const api = useMyApi(); const items = useQuery(api.listItems.queryOptions());
if (items.isLoading) { return <LoadingSpinner />; }
if (items.isError) { const err = items.error; switch (err.status) { case 403: // err.error 类型为 ListItems403Response return <ErrorMessage message={err.error.reason} />; case 500: case 502: // err.error 类型为 ListItems5XXResponse return ( <ErrorMessage message={err.error.message} details={`追踪 ID:${err.error.traceId}`} /> ); default: return <ErrorMessage message="未知错误" />; } }
return ( <ul> {items.data.map((item) => ( <li key={item.id}>{item.name}</li> ))} </ul> );}直接处理客户端加载状态
function ItemList() { const api = useMyApiClient(); const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null);
useEffect(() => { const fetchItems = async () => { try { const data = await api.listItems(); setItems(data); } catch (err) { setError(err); } finally { setLoading(false); } }; fetchItems(); }, [api]);
if (loading) { return <LoadingSpinner />; }
if (error) { const err = error as ListItemsError; switch (err.status) { case 403: // err.error 类型为 ListItems403Response return <ErrorMessage message={err.error.reason} />; case 500: case 502: // err.error 类型为 ListItems5XXResponse return ( <ErrorMessage message={err.error.message} details={`追踪 ID:${err.error.traceId}`} /> ); default: return <ErrorMessage message="未知错误" />; } }
return ( <ul> {items.map((item) => ( <li key={item.id}>{item.name}</li> ))} </ul> );}实现乐观更新以提升用户体验:
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
function ItemList() { const api = useMyApi(); const queryClient = useQueryClient();
// 获取项目的查询 const itemsQuery = useQuery(api.listItems.queryOptions());
// 带乐观更新的删除变更 const deleteMutation = useMutation({ ...api.deleteItem.mutationOptions(), onMutate: async (itemId) => { // 取消正在进行的重新获取 await queryClient.cancelQueries({ queryKey: api.listItems.queryKey() });
// 快照先前值 const previousItems = queryClient.getQueryData(api.listItems.queryKey());
// 乐观更新至新值 queryClient.setQueryData( api.listItems.queryKey(), (old) => old.filter((item) => item.id !== itemId) );
// 返回包含快照的上下文对象 return { previousItems }; }, onError: (err, itemId, context) => { // 变更失败时使用 onMutate 返回的上下文回滚 queryClient.setQueryData(api.listItems.queryKey(), context.previousItems); console.error('删除项目失败:', err); }, onSettled: () => { // 无论成功或失败都重新获取以确保数据同步 queryClient.invalidateQueries({ queryKey: api.listItems.queryKey() }); }, });
if (itemsQuery.isLoading) { return <LoadingSpinner />; }
if (itemsQuery.isError) { return <ErrorMessage message="加载项目失败" />; }
return ( <ul> {itemsQuery.data.map((item) => ( <li key={item.id}> {item.name} <button onClick={() => deleteMutation.mutate(item.id)} disabled={deleteMutation.isPending} > {deleteMutation.isPending ? '删除中...' : '删除'} </button> </li> ))} </ul> );}直接使用客户端实现乐观更新
function ItemList() { const api = useMyApiClient(); const [items, setItems] = useState([]);
const handleDelete = async (itemId) => { // 乐观移除项目 const previousItems = items; setItems(items.filter((item) => item.id !== itemId));
try { await api.deleteItem(itemId); } catch (error) { // 错误时恢复先前项目 setItems(previousItems); console.error('删除项目失败:', error); } };
return ( <ul> {items.map((item) => ( <li key={item.id}> {item.name} <button onClick={() => handleDelete(item.id)}>删除</button> </li> ))} </ul> );}集成提供完整的端到端类型安全。IDE 将为所有 API 调用提供自动补全和类型检查:
import { useMutation } from '@tanstack/react-query';
function ItemForm() { const api = useMyApi();
// 类型安全的创建项目变更 const createItem = useMutation({ ...api.createItem.mutationOptions(), // ✅ 如果 onSuccess 回调未处理正确的响应类型会报类型错误 onSuccess: (data) => { // data 类型基于 API 的响应模式 console.log(`项目创建 ID:${data.id}`); }, });
const handleSubmit = (data: CreateItemInput) => { // ✅ 如果输入不匹配模式会报类型错误 createItem.mutate(data); };
// 错误 UI 可使用类型收窄处理不同错误类型 if (createItem.error) { const error = createItem.error; switch (error.status) { case 400: // error.error 类型为 CreateItem400Response return ( <FormError message="无效输入" errors={error.error.validationErrors} /> ); case 403: // error.error 类型为 CreateItem403Response return <AuthError reason={error.error.reason} />; default: // error.error 类型为 CreateItem5XXResponse(500、502 等) return <ServerError message={error.error.message} />; } }
return ( <form onSubmit={(e) => { e.preventDefault(); handleSubmit({ name: '新项目' }); }}> {/* 表单字段 */} <button type="submit" disabled={createItem.isPending} > {createItem.isPending ? '创建中...' : '创建项目'} </button> </form> );}直接使用客户端的类型安全
function ItemForm() { const api = useMyApiClient(); const [error, setError] = useState<CreateItemError | null>(null);
const handleSubmit = async (data: CreateItemInput) => { try { // ✅ 输入不匹配模式会报类型错误 await api.createItem(data); } catch (e) { // ✅ 错误类型包含所有可能的错误响应 const err = e as CreateItemError; switch (err.status) { case 400: // err.error 类型为 CreateItem400Response console.error('验证错误:', err.error.validationErrors); break; case 403: // err.error 类型为 CreateItem403Response console.error('未授权:', err.error.reason); break; case 500: case 502: // err.error 类型为 CreateItem5XXResponse console.error( '服务器错误:', err.error.message, '追踪:', err.error.traceId, ); break; } setError(err); } };
// 错误 UI 可使用类型收窄处理不同错误类型 if (error) { switch (error.status) { case 400: return ( <FormError message="无效输入" errors={error.error.validationErrors} /> ); case 403: return <AuthError reason={error.error.reason} />; default: return <ServerError message={error.error.message} />; } }
return <form onSubmit={handleSubmit}>{/* ... */}</form>;}类型根据 FastAPI 的 OpenAPI 模式自动生成,确保 API 的更改在构建后反映到前端代码中。
Custom Auth
Section titled “Custom Auth”If your FastAPI uses Custom authentication (Lambda Authorizer), you will need to edit the generated client provider to add the authorization headers your authorizer expects. Look for the fetch configuration in the generated <ApiName>Provider.tsx and add your token or API key to the request headers.