Mastra React 最佳实践:保持组件与 Hook 的输入输出 API 精简(Keep Input and Output APIs Narrow)

发布时间:2026/9/12 18:20:48
Mastra React 最佳实践:保持组件与 Hook 的输入输出 API 精简(Keep Input and Output APIs Narrow) Mastra React 最佳实践保持组件与 Hook 的输入输出 API 精简Keep Input and Output APIs Narrow【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra本篇技术指南以 Mastra 仓库内.claude/skills/react-best-practices技能中的structure-narrow-apis规则为骨架讲解如何在 React 组件、Hook、函数与工具函数的设计中保持 API 精简当输入参数与返回值膨胀到涵盖多个无关职责时应当拆分并在组件层完成组合而不是简单地把一堆参数塞进一个对象。读完本文你将掌握窄 API 的判别方法而非机械的参数数量阈值、从巨型 Hook 拆分到组件组合的完整重构路径以及用于代码评审的 review smells 检查清单。规则定位Component Structure 类别中的可维护性规则在 Mastra Engineering 维护的 React Best Practices 技能 中structure-narrow-apis归属于Component Structure组件结构类别。该技能共包含 26 条规则、9 个类别按影响优先级排序其中组件结构类别的官方影响评级为MEDIUM-HIGH可维护性与testing-*正确性类别的优先级并列仅次于 Type Safety 类别。根据规则目录中的定位本规则与同类的其他规则共同约束领域组件的结构structure-single-responsibility一个组件/Hook 一个职责 一个文件定义职责归属与文件边界structure-narrow-apis本篇拆分 props、参数与返回值过大的单元把值包装进一个对象并不能减少职责用 API 宽度作为职责边界需要拆分的信号structure-composition-over-config固定集合的项目用「每项一个组件」显式组合不要用配置数组 map 出组件structure-derive-dont-duplicate能从已有参数推导出的值不要作为独立参数再传一次规则文件本身带有结构化元信息frontmatter其 impact 描述为oversized APIs hide mixed responsibilities, couple callers to unrelated behavior, and make units harder to understand, test, change, and reuse过大的 API 隐藏了混合的职责把调用方耦合到无关行为上并让单元更难理解、测试、修改和复用——这是理解本规则价值的关键起点。核心定义窄 API 的本质是「聚焦一个职责」而不是「参数很少」规则的第一句话即为定义A component, hook, function, or utility must expose a focused API. Requiring many unrelated props or arguments, or returning a large bag of unrelated values and handlers, is evidence that the unit owns too many responsibilities.组件、Hook、函数或工具函数必须暴露聚焦的 API。如果它要求很多彼此无关的 props 或参数或者返回一大包彼此无关的值和处理函数这就是该单元拥有过多职责的证据。需要特别强调的是判别标准判断的依据是「这些值是否属于同一个内聚的职责」而不是「API 是否低于某个数字阈值」。规则原文明确指出There is no universal maximum count. A hook accepting 16 inputs and returning an object with 30 fields is an obvious review smell, but those numbers are deliberately arbitrary. Judge whether the values belong to one cohesive responsibility, not whether the API falls just below a numeric threshold.即不存在普适的最大参数数量。一个接受 16 个输入、返回含 30 个字段对象的 Hook 显然是评审中的味道review smell但这两个数字是刻意给出的示例而非硬性标准。真正的判断标准是这些值是否服务于同一个内聚的职责。一个 Hook 接受 3 个高度相关的参数、却涉及查询、分页、选中与编辑四个职责同样是不合格的而一个配置项集合如果本来就属于同一概念十几个字段也可能是合理的。常见误区把无关参数打包进一个对象 ≠ API 变窄规则原文用一个专门的反例澄清最普遍的误解Moving the same values into one parameter object doesnotmake the API narrow. This changes the syntax, not the responsibility boundary.把同样的值移进一个参数对象并不会让 API 变窄——这改变的只是语法而不是职责边界。// Still an oversized API: one object contains the same unrelated inputs. useWorkspaceController({ workspaceId, query, sort, page, selectedIds, draftName, draftDescription, permissions, // ...more unrelated inputs });这个示例中workspaceId工作区标识、query/sort/page搜索与分页、selectedIds多选状态、draftName/draftDescription草稿表单、permissions权限被装进同一个对象参数看起来「参数变少了」但调用方依然被强迫为所有职责提供输入——包括它根本不关心的草稿字段和权限字段。包装成对象只改变了语法形态责任的边界原封未动。这也是许多「参数收敛重构」失败的根源对象化隐藏了问题而非解决了问题。反例拆解一个 Hook 同时拥有查询、分页、选中与编辑四个职责规则给出了完整的「错误写法」示例——useWorkspaceController同时承担了数据查询、筛选、分页、多选、权限判断与表单状态管理function useWorkspaceController({ workspaceId, query, sort, page, pageSize, selectedIds, draftName, draftDescription, canEdit, onSave, // ...more inputs }: WorkspaceControllerOptions) { // Fetching, filtering, pagination, selection, permissions, and form state. return { items, total, isLoading, error, query, setQuery, sort, setSort, page, setPage, selectedIds, selectItem, clearSelection, draftName, setDraftName, draftDescription, setDraftDescription, save, canSave, // ...more unrelated values and handlers }; }逐条分析这个 Hook 的问题输入侧跨域pageSize、query、sort属于列表浏览selectedIds、selectItem属于交互状态draftName、draftDescription属于编辑表单canEdit、onSave属于权限与提交逻辑——调用方必须一次性为四个互不相关的职责提供全部输入。输出侧爆炸返回对象同时包含查询结果items/total/isLoading/error、分页状态page/setPage、选择状态selectedIds/selectItem/clearSelection、表单状态draftName/setDraftName/...与保存逻辑save/canSave。耦合与难测任何一处职责的改动比如分页从客户端切到服务端、增加一列可排序字段都会触碰同一个 Hook 的签名与返回结构波及所有调用方测试一个「只关心列表」的场景也被迫 mock 表单相关的全部输入输出。从源码结构看这正是 Mastra 的 Component Structure 类别焦点 所警告的Blasted components are hard to test, review, and reuse, and unrelated state changes re-render everything臃肿的组件难以测试、评审和复用且无关的状态变更会触发整体重渲染。正解拆解拆分职责在组件层组合规则给出的正确写法把四个职责拆成四个聚焦的 Hook再由页面组件WorkspacePage负责组装function useWorkspaceSearch(workspaceId: string) { const [query, setQuery] useState(); const [sort, setSort] useStateWorkspaceSort(updated); const result useWorkspaceItems({ workspaceId, query, sort }); return { query, setQuery, sort, setSort, ...result }; } function usePagination(total: number) { const [page, setPage] useState(1); return { page, setPage, pageCount: Math.ceil(total / 20) }; } function useSelection() { const [selectedIds, setSelectedIds] useStatestring[]([]); return { selectedIds, setSelectedIds }; } function WorkspacePage({ workspaceId }: { workspaceId: string }) { const search useWorkspaceSearch(workspaceId); const pagination usePagination(search.total); const selection useSelection(); return WorkspaceView search{search} pagination{pagination} selection{selection} /; }这个重构的要点值得逐条展开每个 Hook 只暴露一种职责的最小面usePagination(total)只关心总条数与每页 20 条的页数计算输入仅一个total输出仅{ page, setPage, pageCount }useSelection()只维护selectedIds数组。Hook 之间通过返回值协作WorkspacePage把useWorkspaceSearch返回的total作为usePagination的输入——数据流显式、可读且没有引入任何中间「胶水」状态。职责间的数据通过显式依赖传递useWorkspaceSearch内部把{ workspaceId, query, sort }传给useWorkspaceItems数据获取逻辑被收敛在一个更小的单元里。组件/容器是组装点规则原文紧接着给出了一个重要约束The component or container is the assembly point: it may compose several focused hooks and pass each focused result to the child that needs it. Do not create another mega-hook merely to hide composition from the component.组件或容器是组装点它可以把多个聚焦的 Hook 组合起来并把每个聚焦结果传给需要它的子组件。不要为了「从组件中隐藏组合细节」而再造一个巨型 Hook——那只是把useWorkspaceController换了个名字重写一遍职责边界依旧没有变窄。WorkspacePage的唯一职责就是组合composition这与structure-single-responsibility规则中「页面/容器组件的单一职责就是组装」的表述完全一致。拆分同时收窄重渲染范围与 structure-single-responsibility 中「拆分还能收窄重渲染范围在筛选框打字不再触发表单重渲染」的收益同理本规则拆出的useSelection、usePagination各自的状态变更只会触发依赖对应返回值的那部分 UI 重渲染而不是整个工作区视图。规则延伸不止于 Hook——组件、函数与返回值的四个落点规则原文明确要求把同样的原则应用到 Hook 之外的场景共四条拆分组件如果一个组件的 props 跨越了多个无关领域把它拆成聚焦的子组件Split a component whose props span unrelated domains into focused child components.。这与同目录下的 structure-composition-over-config 互为印证——后者要求「固定集合的每一项都写成拥有自己数据与加载状态的独立组件」而不是定义一个Capability[]配置数组再.map成组件。拆分函数/工具函数如果函数需要彼此无关的参数组拆成内聚的操作Split a function or utility that needs unrelated argument groups into cohesive operations.。只返回该职责拥有的值不要「以防万一」暴露内部状态或无关的便利字段Return only the values owned by that responsibility; do not expose internal state or unrelated convenience fields just in case.。返回值中每个字段都应该被至少一个真实调用方用到否则就是不必要的 API 面。按概念分组而非掩盖长参数表当一组值确实构成一个有意义的整体概念时才把它们归入对象而不是为了掩饰过长的参数列表Group values into an object when they form one meaningful concept, not to disguise a long argument list.。第 4 点与前面的「对象化误区」形成闭环对象分组的标准是「是否构成一个概念」而不是「参数看起来是否更少」。评审清单五类 Review Smells规则为代码评审提供了可操作的信号列表只要命中任意一条就该考虑拆分调用方传入他们自己并不使用的值callers pass values they do not otherwise use——说明 API 在为其他职责的调用方买单大多数调用方只解构返回对象的一小部分字段most callers destructure only a small subset of a return object——说明返回面远大于真实消费面输入与输出各自形成明显的命名簇inputs and outputs fall into named clusters——例如「查询簇」「分页簇」「表单簇」同时出现在一个签名里为无关功能做的改动反复触碰同一个 APIchanges for unrelated features keep touching the same API——这是职责耦合的直接证据这个单元无法用一个词描述必须用「…和…」the unit cannot be described without and——比如「这个 Hook 负责查询和分页和表单」无法命名本身就说明职责过多。这些 smells 与同目录structure-single-responsibility.md中「多个无关的useState/useQuery簇」「注释头分隔的『章节』」「不能用 And 命名的组件」等触发条件高度同构——API 宽度正是职责边界是否需要拆分的「外部体检指标」。仓库实践佐证Mastra React SDK 中真实的窄 API 形态structure-narrow-apis并非空泛的教条——Mastra 自身的 React 客户端 SDK 中的真实 Hook 就体现着「输入聚焦 返回分组」的结构可以从源码中对照印证。useStreamWorkflow三个配置输入 六项分组返回workflows/use-stream-workflow.ts 中useStreamWorkflow的签名只有三个配置项{ debugMode, tracingOptions, onError }它们同属于「流式工作流的运行配置」这一内聚概念返回对象则包含streamWorkflow、streamResult、isStreaming、observeWorkflowStream、closeStreamsAndReset、resumeWorkflowStream、timeTravelWorkflowStream等。每个返回字段都对应真实的 API 行为由useMutationlib/use-mutation.ts与若干useRef流句柄支撑没有任何「以防万一」的便利字段——调用方按需解构即可。ModelSettings按概念归组而非拼凑agent/types.ts 中的ModelSettings接口把frequencyPenalty、maxTokens、temperature、topP等采样参数与instructions、system、providerOptions、requireToolApproval等控制参数收拢在同一个「模型设置」概念下——字段虽多但每个字段都属于「一次模型调用配置」这一内聚领域调用方在语义上必须整体感知它们这正是规则第 4 点「值构成一个有意义的整体概念时归入对象」的正面案例。同时其 JSDoc 注释精确区分了instructions替换与system追加的语义避免调用方被迫同时理解两种行为。相关规则的配套约束窄 API 与另外几条结构规则在真实 SDK 中是配套出现的useStreamWorkflow的onError: (error, defaultMessage)回调签名保持单一职责不要求调用方传入用于错误处理的无关状态composition-over-config 中提到 TanStack Query 会在实例间去重请求参见 client-request-dedupe因此「每个子组件自己发起查询」的拆分方案不会带来多余的网络请求——这为「组件层组合 每项一个组件」提供了性能前提derive-dont-duplicate 与本规则互补前者禁止「能推导的值重复传入」如同时传count和minTimes后者禁止「无关的值打包传入」——两者共同保证每个参数都是调用方必须提供的、独立的最小事实来源。在 Mastra 项目中的应用建议根据 SKILL.md 的When to Apply说明这套准则适用于以下场景编写新的 React 组件Writing new React components实现数据获取逻辑Implementing data fetching评审代码中的性能与结构问题Reviewing code for performance issues重构现有 React 代码Refactoring existing React code。对应到structure-narrow-apis规则落地动作可以总结为三步写代码时为每个组件/Hook/函数预设一个「一句话职责」凡是签名或返回值无法被这句话完全覆盖立即考虑拆分重构时按上文正解模式把巨型 Hook 拆成useWorkspaceSearch式的聚焦 Hook让页面/容器组件作为唯一的组装点严禁用「再包一层 mega-hook」的方式掩盖组合评审时逐条对照五类 review smells尤其关注「大多数调用方只解构一小部分返回值」和「无法用一句话描述这个单元」这两个最容易被忽视的信号。小结structure-narrow-apis规则源文件位于 .claude/skills/react-best-practices/references/rules/structure-narrow-apis.md的核心结论可以浓缩为一句话API 的宽度是职责宽度的投影——把无关输入塞进一个对象只是改变语法真正的解法是拆分职责并在组件层组装。它与structure-single-responsibility职责与文件边界、structure-composition-over-config组件级显式组合、structure-derive-dont-duplicate参数去重共同构成了 Mastra React 代码结构约束的完整拼图而 Mastra React SDK 中useStreamWorkflow、ModelSettings等真实代码正是这套原则在实践中的注脚。【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考