mcpField
F
feature-systems-pattern

feature-systems-pattern

github.com/marcioaltoe开发049 次安装1 天前更新
代码仓库

关于 feature-systems-pattern

feature-systems-pattern 是用于在 TypeScript 项目中组织领域模块的开发技能,指导开发者构建 API、查询层、hooks、组件和公共导出,并规定依赖流向与缓存处理方式。

主要功能

  1. 01按领域组织系统目录
  2. 02类型化 API 适配器与错误
  3. 03TanStack Query 键与选项
  4. 04hooks、变更操作与乐观更新
  5. 05Context 与 XState 存储模式
  6. 06依赖流向与缓存范围规则

适用场景

  1. 01搭建新的领域模块
  2. 02添加查询与变更 hooks
  3. 03处理乐观缓存更新
  4. 04检查系统边界与导出

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/marcioaltoe/skills --skill feature-systems-pattern

安装后在 Claude Code 中输入/feature-systems-pattern确认已加载。

附带文件references/directory-layout.mdreferences/patterns.md

详细介绍

功能系统指南

“系统”是一个自包含、以领域为中心的模块,负责该领域相关的一切内容:API 调用、查询层、钩子、组件和公共 API。系统位于 systems/<domain>/ 目录下。

阅读 references/directory-layout.md,了解完整的目录结构和命名约定。 阅读 references/patterns.md,查看各层的注释实现模式。

快速参考

必须搭配的技能

使用此技能时一并启用这些技能——系统横跨多个技术领域:

情况启用
任何钩子或组件react + tanstack-query
数据获取/缓存tanstack-query
变更tanstack-query
XState 存储xstate
实用函数es-toolkit
编写/修复测试testing-boss + vitest
Bug 修复systematic-debugging + no-workarounds
系统目录一览
systems/<domain>/
├── index.ts               # Public API barrel — required for every system
├── types.ts               # TypeScript types for this domain
├── adapters/              # API service layer (HTTP calls, error types)
│   └── <domain>-api.ts
├── lib/                   # Pure utilities, schemas, constants, query keys
│   ├── query-keys.ts      # TanStack Query key factory
│   ├── query-options.ts   # Reusable queryOptions / mutationOptions
│   ├── <domain>-schemas.ts
│   └── constants.ts
├── hooks/                 # React hooks (queries, mutations, view-models)
│   ├── __tests__/
│   ├── use-<action>.ts    # Query hooks
│   ├── use-create-<entity>.ts  # Mutation hooks
│   ├── use-update-<entity>.ts
│   ├── use-delete-<entity>.ts
│   └── use-<domain>-view-model.ts
├── contexts/              # React contexts + providers
│   └── <domain>-context.tsx
├── stores/                # XState stores (complex async state machines)
│   └── <domain>-store.ts
├── components/            # React UI components
│   ├── stories/
│   └── index.ts
└── guards/                # Route guards / access checks

分步指南:创建新系统

步骤 1 — 定义 types.ts
  • 导出清晰的领域类型;切勿暴露原始 API 响应结构。
  • 有项目 API 合约类型时,优先基于这些类型派生。
  • 使用 JSDoc 说明复杂的聚合类型,解释其派生规则和不变量。
步骤 2 — 构建 API 服务层
  • 创建 adapters/<domain>-api.ts。
  • 使用项目的 HTTP 客户端调用 API。
  • 导出单个命名空间对象:export const <domain>Api = { list, create, update, delete }。
  • 导出带类型的错误类:export class <Domain>ApiError extends Error { ... }。
  • 每个函数都接受 signal?: AbortSignal,以支持查询取消。
  • 将所有内部辅助函数(错误提取、响应规范化)设为模块私有。
步骤 3 — 添加 lib/query-keys.ts
export const <domain>Keys = {
  all: ["<domain>"] as const,
  lists: () => [...<domain>Keys.all, "list"] as const,
  list: (scopeId: string | null) => [...<domain>Keys.lists(), scopeId] as const,
  details: () => [...<domain>Keys.all, "detail"] as const,
  detail: (id: string) => [...<domain>Keys.details(), id] as const,
};
  • 使用分层键结构,以便精细失效。
  • 使用任何能正确隔离缓存的标识符(userId、orgId 等)限定键的作用域。
  • 对每个键元组都使用 as const。
步骤 4 — 添加 lib/query-options.ts
import { queryOptions } from "@tanstack/react-query";
import { <domain>Api } from "../adapters/<domain>-api";
import { <domain>Keys } from "./query-keys";

export function <domain>ListOptions(scopeId: string | null) {
  return queryOptions({
    queryKey: <domain>Keys.list(scopeId),
    queryFn: ({ signal }) => <domain>Api.list(scopeId!, signal),
    staleTime: 60_000,
    enabled: Boolean(scopeId),
  });
}

export function <domain>DetailOptions(id: string) {
  return queryOptions({
    queryKey: <domain>Keys.detail(id),
    queryFn: ({ signal }) => <domain>Api.get(id, signal),
    enabled: Boolean(id),
  });
}
  • 使用 queryOptions 将 queryKey 和 queryFn 放在一起,确保类型安全并支持复用。
  • 导出每个选项工厂,供钩子、预取和路由加载器使用。
  • 始终将查询上下文中的 signal 传递给 API 层。
步骤 5 — 编写钩子
  • 查询钩子:使用 queryOptions 工厂封装 useQuery;接受一个作用域 ID 和可选的 { enabled? }。
  • 变更钩子:使用 useMutation,并通过适当的 onMutate / onError / onSettled 回调实现乐观更新。
  • 视图模型钩子:组合多个钩子,供页面/外壳组件使用;返回扁平对象。
  • 将测试放在 hooks/__tests__/ 中,或与 use-xxx.test.tsx 一同存放。

阅读 references/patterns.md,了解完整的变更和乐观更新模式。

步骤 6 —(可选)添加上下文

当查询数据或组合状态需要在组件子树中共享,且不想逐层传递属性时,创建 contexts/<domain>-context.tsx。

// Always nullable context — consumer hook throws if used outside provider
export const <Domain>Context = createContext<<Domain>ContextValue | null>(null);
  • 在同一文件中导出上下文、提供程序组件,以及重新导出的使用者钩子。
  • 对于性能敏感的组件树,可拆分为 Core / UI / Operations 子上下文。
步骤 7 —(可选)添加 XState 存储

对于复杂的异步状态机(多步骤流程、轮询、事件发射),创建 stores/<domain>-store.ts。

export const <domain>Store = createStore({
  context: { ... } as <Domain>Context,
  emits: { ... },
  on: {
    someEvent: (context, event, enqueue) => {
      enqueue.effect(async () => { ... });
      return { ...context, isLoading: true };
    },
  },
});
步骤 8 — 配置 index.ts

使用带标签的分区和显式命名导出组织 barrel 文件:

// Types
export type { <Domain>Type } from "./types";

// Hooks
export { use<Domain>List, use<Domain>Detail } from "./hooks";
export { useCreate<Domain>, useUpdate<Domain>, useDelete<Domain> } from "./hooks";

// Components
export { <Domain>Component } from "./components";

// Utilities
export { <domain>HelperFn } from "./lib/<domain>-utils";

// Query Keys & Options
export { <domain>Keys } from "./lib/query-keys";
export { <domain>ListOptions, <domain>DetailOptions } from "./lib/query-options";

// API
export { <domain>Api, <Domain>ApiError } from "./adapters/<domain>-api";

关键规则

  1. 使用 queryOptions 实现共置。 在可复用的选项工厂中共置 queryKey 和 queryFn。切勿在多个文件中分散使用同一个查询键。
  2. 依赖关系单向流动。 adapters -> lib -> hooks -> components。Adapters 绝不能导入 hooks 或 components。
  3. 限定查询键的作用域。 任何依赖身份验证作用域(用户、组织、租户)的查询,都必须在其键中包含该作用域 ID,以防止跨作用域的陈旧数据。
  4. 在 API 层使用类型化错误。 绝不能从 adapters 抛出原始错误。使用类型化错误类,以便使用方无需检查错误消息字符串即可区分错误类型。
  5. 传递 AbortSignal。 将 queryFn 上下文中的 signal 传递给每个 API 调用,以正确取消查询。
  6. 变更后始终使查询失效。 在 onSettled 中使用 queryClient.invalidateQueries,确保最终与服务器保持一致。
  7. 乐观更新必须支持回滚。 使用基于缓存的乐观更新时,在 onMutate 中保存之前的数据,并在 onError 中恢复。
  8. 乐观更新前取消正在进行的查询。 在 onMutate 中调用 queryClient.cancelQueries,防止重新获取的数据覆盖乐观状态。
  9. 将 Zod schemas 放在 lib/ 中。 将所有 Zod schemas 放在 lib/<domain>-schemas.ts 中,以便在 API 边界进行运行时验证。

错误处理

  • API 层抛出类型化错误:TanStack Query 会捕获该错误,并通过 query.error 暴露。
  • 带乐观更新的变更失败:onError 回调会将缓存回滚到 onMutate 保存的快照,然后 onSettled 使查询失效,以重新获取最新数据。
  • 跨作用域的陈旧数据:将作用域 ID 添加到查询键中,并确认 enabled 守卫会检查 Boolean(scopeId)。
  • 卸载时取消查询:组件卸载时,TanStack Query 会通过 signal 自动取消正在进行的查询——确保将 signal 传递到 API 层。

详细参考

  • references/directory-layout.md — 完整的目录结构、文件命名和 barrel 约定
  • references/patterns.md — API 层、查询选项、hooks、变更、乐观更新、上下文和 stores 的注释代码模式

评论

支持 Markdown