React 连接 Smithy API
connection 生成器提供了一种快速将 React 网站与 Smithy TypeScript API 后端集成的方式。它设置了所有必要的配置,以类型安全的方式连接到您的 Smithy API,包括客户端和 TanStack Query 钩子生成、AWS IAM 和 Cognito 身份验证支持以及正确的错误处理。
在使用此生成器之前,请确保您的 React 应用程序具备以下条件:
- 一个渲染应用程序的
main.tsx文件 - 正常运行的 Smithy TypeScript API 后端(使用
ts#smithy-api生成器生成) - 如果连接的 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)。使用 '.' 显式选择项目作为目标。 |
生成器将对 React 应用程序中的以下文件进行更改:
文件夹src
文件夹components
- <ApiName>Provider.tsx API 客户端提供者
- QueryClientProvider.tsx TanStack React Query 客户端提供者
文件夹RuntimeConfig/ 本地开发运行时配置组件
- …
文件夹hooks
- use<ApiName>.tsx 添加通过 TanStack Query 管理状态的 API 调用钩子
- use<ApiName>Client.tsx 添加用于实例化普通 API 客户端的钩子
- useSigV4.tsx 添加用于 SigV4 签名 HTTP 请求的钩子(如果选择 IAM 身份验证)
- project.json 添加生成类型安全客户端的新构建目标
- .gitignore 默认忽略生成的客户端文件
生成器还会向 Smithy 模型添加文件:
文件夹model
文件夹src
- extensions.smithy 定义用于自定义生成客户端的特性
如果尚未存在,生成器还会向网站基础设施添加运行时配置,确保 Smithy API 的 API URL 在网站中可用,并由 use<ApiName>.tsx 钩子自动配置。
在构建时,根据 Smithy API 的 OpenAPI 规范生成类型安全客户端。这将向 React 应用程序添加三个新文件:
文件夹src
文件夹generated
文件夹<ApiName>
- types.gen.ts 从 Smithy 模型结构生成的类型
- client.gen.ts 调用 API 的类型安全客户端
- options-proxy.gen.ts 提供创建 TanStack Query 钩子选项的方法
使用生成的代码
Section titled “使用生成的代码”生成的类型安全客户端可用于从 React 应用程序调用 Smithy API。建议通过 TanStack Query 钩子使用客户端,但也可以直接使用普通客户端。
使用 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>Loading...</div>; if (item.isError) return <div>Error: {item.error.message}</div>;
return <div>Item: {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>Loading...</div>; if (error) return <div>Error: {error.message}</div>;
return <div>Item: {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: 'New Item', description: 'A new item' }); };
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: (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> );}生成的钩子会自动处理基于游标的分页。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 类型封装了 Smithy 模型中定义的可能错误响应。每个错误都有 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> </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> </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> </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> </div> ); } }
return <button onClick={handleClick}>创建项目</button>;}自定义生成代码
Section titled “自定义生成代码”在目标 Smithy model 项目的 extensions.smithy 中添加了多个 Smithy 特性,可用于自定义生成的客户端。
查询与变更操作
Section titled “查询与变更操作”默认情况下,Smithy API 中使用 HTTP 方法 PUT、POST、PATCH 和 DELETE 的操作被视为变更操作,其他视为查询操作。
可通过在模型项目的 extensions.smithy 中使用 @query 和 @mutation 特性更改此行为。
@query
Section titled “@query”应用 @query 特性强制将操作视为查询:
@http(method: "POST", uri: "/items")@queryoperation ListItems { input: ListItemsInput output: ListItemsOutput}生成的钩子将提供 queryOptions,即使使用 POST 方法:
const items = useQuery(api.listItems.queryOptions());@mutation
Section titled “@mutation”应用 @mutation 特性强制将操作视为变更:
@http(method: "GET", uri: "/start-processing")@mutationoperation StartProcessing { input: StartProcessingInput output: StartProcessingOutput}生成的钩子将提供 mutationOptions,即使使用 GET 方法:
const startProcessing = useMutation(api.startProcessing.mutationOptions());自定义分页游标
Section titled “自定义分页游标”默认情况下,生成的钩子假设分页游标参数名为 cursor。可通过在模型项目的 extensions.smithy 中使用 @cursor 特性自定义此行为。
应用 @cursor 特性并指定 inputToken 以更改分页令牌的输入参数名称:
@http(method: "GET", uri: "/items")@cursor(inputToken: "nextToken")operation ListItems { input := { nextToken: String limit: Integer } output := { items: ItemList nextToken: String }}如需禁用具有 cursor 输入参数的操作的分页生成:
@cursor(enabled: false)operation ListItems { input := { // 默认情况下,名为 'cursor' 的输入参数会被视为分页操作 cursor: String } output := { ... }}生成的钩子和客户端方法根据 Smithy 操作的 @tags 特性自动组织。具有相同标签的操作会被分组,有助于保持 API 调用有序,并提供更好的 IDE 代码补全。
例如,使用以下 Smithy 模型:
service MyService { operations: [ListItems, CreateItem, ListUsers, CreateUser]}
@tags(["items"])operation ListItems { input: ListItemsInput output: ListItemsOutput}
@tags(["items"])operation CreateItem { input: CreateItemInput output: CreateItemOutput}
@tags(["users"])operation ListUsers { input: ListUsersInput output: ListUsersOutput}
@tags(["users"])operation CreateUser { input: CreateUserInput output: CreateUserOutput}生成的钩子将按标签分组:
import { useQuery, useMutation } from '@tanstack/react-query';import { useMyApi } from './hooks/useMyApi';
function ItemsAndUsers() { const api = useMyApi();
// 项目操作分组在 api.items 下 const items = useQuery(api.items.listItems.queryOptions()); const createItem = useMutation(api.items.createItem.mutationOptions());
// 用户操作分组在 api.users 下 const users = useQuery(api.users.listUsers.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);
// 项目操作分组在 api.items 下 const itemsData = await api.items.listItems(); setItems(itemsData);
// 用户操作分组在 api.users 下 const usersData = await api.users.listUsers(); setUsers(usersData); } catch (error) { console.error('获取数据错误:', error); } finally { setIsLoading(false); } };
fetchData(); }, [api]);
const handleCreateItem = async () => { try { // 使用分组方法创建项目 const newItem = await api.items.createItem({ 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> );}可在 Smithy 模型中定义自定义错误结构来自定义错误响应。生成的客户端会自动处理这些自定义错误类型。
定义自定义错误结构
Section titled “定义自定义错误结构”在 Smithy 模型中定义错误结构:
@error("client")@httpError(400)structure InvalidRequestError { @required message: String
fieldErrors: FieldErrorList}
@error("client")@httpError(403)structure UnauthorizedError { @required reason: String}
@error("server")@httpError(500)structure InternalServerError { @required message: String
traceId: String}
list FieldErrorList { member: FieldError}
structure FieldError { @required field: String
@required message: String}为操作添加错误
Section titled “为操作添加错误”指定操作可能返回的错误:
operation CreateItem { input: CreateItemInput output: CreateItemOutput errors: [ InvalidRequestError UnauthorizedError InternalServerError ]}
operation GetItem { input: GetItemInput output: GetItemOutput errors: [ ItemNotFoundError InternalServerError ]}
@error("client")@httpError(404)structure ItemNotFoundError { @required message: String}在 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) => { // 错误类型基于 Smithy 模型中的错误定义 switch (error.status) { case 404: // error.error 类型为 ItemNotFoundError console.error('未找到:', error.error.message); break; case 500: // error.error 类型为 InternalServerError console.error('服务器错误:', error.error.message); console.error('追踪 ID:', error.error.traceId); break; } } });
// 带类型错误处理的变更操作 const createItem = useMutation({ ...api.createItem.mutationOptions(), onError: (error) => { switch (error.status) { case 400: // error.error 类型为 InvalidRequestError console.error('验证错误:', error.error.message); console.error('字段错误:', error.error.fieldErrors); break; case 403: // error.error 类型为 UnauthorizedError console.error('未授权:', error.error.reason); break; } } });
// 组件渲染与错误处理 if (getItem.isError) { if (getItem.error.status === 404) { return <NotFoundMessage message={getItem.error.error.message} />; } else if (getItem.error.status === 500) { 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) { // 错误类型基于 Smithy 模型中的错误定义 const err = e as GetItemError; setError(err);
switch (err.status) { case 404: // err.error 类型为 ItemNotFoundError console.error('未找到:', err.error.message); break; case 500: // err.error 类型为 InternalServerError console.error('服务器错误:', err.error.message); console.error('追踪 ID:', err.error.traceId); 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 类型为 InvalidRequestError console.error('验证错误:', err.error.message); console.error('字段错误:', err.error.fieldErrors); break; case 403: // err.error 类型为 UnauthorizedError console.error('未授权:', err.error.reason); break; } } };
// 组件渲染与错误处理 if (loading) { return <LoadingSpinner />; }
if (error) { if (error.status === 404) { return <NotFoundMessage message={error.error.message} />; } 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} /> ); 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} /> ); 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) => { // 变更失败时回滚数据 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 类型为 InvalidRequestError return ( <FormError message="无效输入" errors={error.error.fieldErrors} /> ); case 403: // error.error 类型为 UnauthorizedError return <AuthError reason={error.error.reason} />; default: // error.error 类型为 InternalServerError(500 等) 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 类型为 InvalidRequestError console.error('验证错误:', err.error.fieldErrors); break; case 403: // err.error 类型为 UnauthorizedError console.error('未授权:', err.error.reason); break; case 500: // err.error 类型为 InternalServerError console.error('服务器错误:', err.error.message); break; } setError(err); } };
// 错误 UI 可使用类型收窄处理不同错误类型 if (error) { switch (error.status) { case 400: return ( <FormError message="无效输入" errors={error.error.fieldErrors} /> ); case 403: return <AuthError reason={error.error.reason} />; default: return <ServerError message={error.error.message} />; } }
return <form onSubmit={handleSubmit}>{/* ... */}</form>;}类型根据 Smithy API 的 OpenAPI 模式自动生成,确保 API 的变更在构建后立即反映到前端代码中。
Custom Auth
Section titled “Custom Auth”If your Smithy API 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.