app-renderer-systems

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Feature Systems Guide

特性系统指南

A "system" is a self-contained, domain-driven module that owns everything related to one domain: its API calls, query layer, hooks, components, and public API. Systems live under a
systems/<domain>/
directory.
Read
references/directory-layout.md
for the full directory structure and naming conventions. Read
references/patterns.md
for annotated implementation patterns per layer.
“系统”是一个独立的、领域驱动的模块,拥有与单个领域相关的所有内容:其API调用、查询层、hooks、组件和公共API。系统存放在
systems/<domain>/
目录下。
完整目录结构和命名规范请阅读
references/directory-layout.md
。 各层带注释的实现模式请阅读
references/patterns.md

Quick Reference

快速参考

Mandatory Companion Skills

必备配套技能

Activate alongside this skill — systems span multiple technical domains:
SituationActivate
Any hook or component
react
+
tanstack-query-best-practices
Data fetching/caching
tanstack-query-best-practices
Mutations
tanstack-query-best-practices
XState store
xstate
Utility functions
es-toolkit
Writing/fixing tests
test-antipatterns
+
vitest
Bug fix
systematic-debugging
+
no-workarounds
使用本指南时请同步激活以下技能——系统横跨多个技术领域:
场景需激活的技能
任意hook或组件开发
react
+
tanstack-query-best-practices
数据获取/缓存
tanstack-query-best-practices
变更操作
tanstack-query-best-practices
XState存储
xstate
工具函数开发
es-toolkit
编写/修复测试
test-antipatterns
+
vitest
Bug修复
systematic-debugging
+
no-workarounds

System Directory at a Glance

系统目录概览

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
systems/<domain>/
├── index.ts               # 公共API桶导出 — 每个系统必备
├── types.ts               # 该领域的TypeScript类型定义
├── adapters/              # API服务层(HTTP调用、错误类型定义)
│   └── <domain>-api.ts
├── lib/                   # 纯工具函数、schema、常量、查询键
│   ├── query-keys.ts      # TanStack Query键工厂
│   ├── query-options.ts   # 可复用的queryOptions / mutationOptions
│   ├── <domain>-schemas.ts
│   └── constants.ts
├── hooks/                 # React hooks(查询、变更、视图模型)
│   ├── __tests__/
│   ├── use-<action>.ts    # 查询hooks
│   ├── use-create-<entity>.ts  # 变更hooks
│   ├── use-update-<entity>.ts
│   ├── use-delete-<entity>.ts
│   └── use-<domain>-view-model.ts
├── contexts/              # React contexts + 提供者
│   └── <domain>-context.tsx
├── stores/                # XState存储(复杂异步状态机)
│   └── <domain>-store.ts
├── components/            # React UI组件
│   ├── stories/
│   └── index.ts
└── guards/                # 路由守卫 / 权限校验

Step-by-Step: Creating a New System

分步指南:创建新系统

Step 1 — Define types.ts

步骤1 — 定义types.ts

  • Export clean domain types; never expose raw API response shapes.
  • Derive from the project's API contract types when available.
  • Document complex aggregated types with JSDoc explaining derivation rules and invariants.
  • 导出清晰的领域类型;永远不要暴露原始API响应结构。
  • 存在项目API契约类型时,从其派生。
  • 用JSDoc注释复杂聚合类型,说明派生规则和不变量。

Step 2 — Build the API service layer

步骤2 — 构建API服务层

  • Create
    adapters/<domain>-api.ts
    .
  • Use the project's HTTP client for API calls.
  • Export a single namespace object:
    export const <domain>Api = { list, create, update, delete }
    .
  • Export a typed error class:
    export class <Domain>ApiError extends Error { ... }
    .
  • Accept
    signal?: AbortSignal
    on every function to support query cancellation.
  • Keep all internal helpers (error extraction, response normalization) private to the module.
  • 创建
    adapters/<domain>-api.ts
  • 使用项目的HTTP客户端发起API调用。
  • 导出单个命名空间对象:
    export const <domain>Api = { list, create, update, delete }
  • 导出带类型的错误类:
    export class <Domain>ApiError extends Error { ... }
  • 每个函数都要接受
    signal?: AbortSignal
    参数以支持查询取消。
  • 所有内部辅助函数(错误提取、响应归一化)都要设为模块私有。

Step 3 — Add lib/query-keys.ts

步骤3 — 添加lib/query-keys.ts

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,
};
  • Use hierarchical key structure for granular invalidation.
  • Scope keys with any identifier (userId, orgId, etc.) that isolates the cache correctly.
  • Use
    as const
    on every key tuple.
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

Step 4 — Add lib/query-options.ts

步骤4 — 添加lib/query-options.ts

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),
  });
}
  • Co-locate
    queryKey
    and
    queryFn
    via
    queryOptions
    for type safety and reuse.
  • Export each option factory for use in hooks, prefetching, and route loaders.
  • Always pass
    signal
    from the query context through to the API layer.
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
    放在一起,保证类型安全和可复用性。
  • 导出每个选项工厂函数,供hooks、预取和路由加载器使用。
  • 始终将查询上下文的
    signal
    传递到API层。

Step 5 — Write hooks

步骤5 — 编写hooks

  • Query hooks: Wrap
    useQuery
    with the
    queryOptions
    factories; accept a scope ID + optional
    { enabled? }
    .
  • Mutation hooks: Use
    useMutation
    with proper
    onMutate
    /
    onError
    /
    onSettled
    callbacks for optimistic updates.
  • View-model hooks: Compose multiple hooks for a page/shell component; return a flat object.
  • Place tests in
    hooks/__tests__/
    or co-locate as
    use-xxx.test.tsx
    .
Read
references/patterns.md
for complete mutation and optimistic update patterns.
  • 查询hooks:用
    queryOptions
    工厂函数封装
    useQuery
    ;接受作用域ID + 可选的
    { enabled? }
    参数。
  • 变更hooks:使用
    useMutation
    并配置合适的
    onMutate
    /
    onError
    /
    onSettled
    回调实现乐观更新。
  • 视图模型hooks:为页面/外壳组件组合多个hooks;返回扁平化对象。
  • 测试代码放在
    hooks/__tests__/
    目录下,或者就近存为
    use-xxx.test.tsx
完整的变更和乐观更新模式请阅读
references/patterns.md

Step 6 — (Optional) Add context

步骤6 —(可选)添加context

Create
contexts/<domain>-context.tsx
when query data or combined state must be shared across a component subtree without prop-drilling.
ts
// Always nullable context — consumer hook throws if used outside provider
export const <Domain>Context = createContext<<Domain>ContextValue | null>(null);
  • Export the context, provider component, and re-export consumer hooks from the same file.
  • For performance-sensitive trees, split into Core / UI / Operations sub-contexts.
当查询数据或组合状态需要在组件子树间共享且不想透传props时,创建
contexts/<domain>-context.tsx
ts
// 始终使用可空context — 若在提供者外使用消费者hook会抛出错误
export const <Domain>Context = createContext<<Domain>ContextValue | null>(null);
  • 在同一个文件中导出context、provider组件,并重新导出消费者hooks。
  • 对性能敏感的子树,可以拆分为核心/UI/操作子context。

Step 7 — (Optional) Add an XState store

步骤7 —(可选)添加XState存储

Create
stores/<domain>-store.ts
for complex async state machines (multi-step flows, polling, event emission).
ts
export const <domain>Store = createStore({
  context: { ... } as <Domain>Context,
  emits: { ... },
  on: {
    someEvent: (context, event, enqueue) => {
      enqueue.effect(async () => { ... });
      return { ...context, isLoading: true };
    },
  },
});
针对复杂异步状态机(多步流程、轮询、事件发射),创建
stores/<domain>-store.ts
ts
export const <domain>Store = createStore({
  context: { ... } as <Domain>Context,
  emits: { ... },
  on: {
    someEvent: (context, event, enqueue) => {
      enqueue.effect(async () => { ... });
      return { ...context, isLoading: true };
    },
  },
});

Step 8 — Wire up index.ts

步骤8 — 配置index.ts

Organize the barrel with labeled sections and explicit named exports:
ts
// 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";
用带标签的区块组织桶导出,使用显式命名导出:
ts
// 类型
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";

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

// 工具函数
export { <domain>HelperFn } from "./lib/<domain>-utils";

// 查询键与选项
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";

Critical Rules

核心规则

  1. Use
    queryOptions
    for co-location.
    Co-locate
    queryKey
    and
    queryFn
    in reusable option factories. Never scatter the same query key across multiple files.
  2. Unidirectional dependency flow.
    adapters -> lib -> hooks -> components
    . Adapters never import from hooks or components.
  3. Scope query keys. Any query depending on an authenticated scope (user, org, tenant) must include that scope ID in its key to prevent stale cross-scope data.
  4. Typed errors in the API layer. Never throw raw errors from adapters. Use a typed error class so consumers can distinguish error types without inspecting message strings.
  5. AbortSignal propagation. Pass
    signal
    from the
    queryFn
    context through to every API call for proper query cancellation.
  6. Always invalidate after mutations. Use
    queryClient.invalidateQueries
    in
    onSettled
    to ensure eventual consistency with the server.
  7. Optimistic updates require rollback. When using cache-based optimistic updates, snapshot previous data in
    onMutate
    and restore in
    onError
    .
  8. Cancel outgoing queries before optimistic updates. Call
    queryClient.cancelQueries
    in
    onMutate
    to prevent refetches from overwriting optimistic state.
  9. Zod schemas in lib/. Place all Zod schemas in
    lib/<domain>-schemas.ts
    for runtime validation at API boundaries.
  1. 使用
    queryOptions
    实现同位置管理
    :将
    queryKey
    queryFn
    放在可复用的选项工厂中,永远不要将同一个查询键分散在多个文件中。
  2. 单向依赖流
    适配器 -> 工具库 -> hooks -> 组件
    。适配器永远不要从hooks或组件导入内容。
  3. 查询键设置作用域:任何依赖认证作用域(用户、组织、租户)的查询,都必须在键中包含对应作用域ID,避免跨作用域数据过期。
  4. API层使用带类型的错误:永远不要从适配器抛出原始错误,使用带类型的错误类,这样消费者无需检查消息字符串就能区分错误类型。
  5. AbortSignal传递:将
    queryFn
    上下文中的
    signal
    传递到每个API调用,实现正确的查询取消。
  6. 变更操作后始终执行失效:在
    onSettled
    中调用
    queryClient.invalidateQueries
    ,保证最终与服务端数据一致。
  7. 乐观更新需要支持回滚:使用基于缓存的乐观更新时,在
    onMutate
    中快照之前的数据,并在
    onError
    中恢复。
  8. 乐观更新前取消正在进行的查询:在
    onMutate
    中调用
    queryClient.cancelQueries
    ,避免重新拉取的数据覆盖乐观状态。
  9. Zod schema存放在lib/目录下:将所有Zod schema放在
    lib/<domain>-schemas.ts
    中,用于API边界的运行时校验。

Error Handling

错误处理

  • API layer throws typed error: TanStack Query catches and exposes it via
    query.error
    .
  • Mutation fails with optimistic update:
    onError
    callback rolls back the cache to the snapshot from
    onMutate
    , then
    onSettled
    invalidates to refetch fresh data.
  • Stale cross-scope data: Add the scope ID to the query key and verify that
    enabled
    guards check
    Boolean(scopeId)
    .
  • Query cancellation on unmount: TanStack Query automatically cancels in-flight queries via the
    signal
    when a component unmounts — ensure
    signal
    is propagated to the API layer.
  • API层抛出带类型的错误:TanStack Query会捕获错误并通过
    query.error
    暴露。
  • 乐观更新的变更操作失败
    onError
    回调将缓存回滚到
    onMutate
    中保存的快照,然后
    onSettled
    执行失效以拉取最新数据。
  • 跨作用域数据过期:在查询键中添加作用域ID,并确认
    enabled
    守卫检查了
    Boolean(scopeId)
  • 组件卸载时的查询取消:TanStack Query会在组件卸载时通过
    signal
    自动取消进行中的查询——请确保
    signal
    已经传递到API层。

Detailed References

详细参考

  • references/directory-layout.md
    — Full directory structure, file naming, and barrel conventions
  • references/patterns.md
    — Annotated code patterns for the API layer, query options, hooks, mutations, optimistic updates, contexts, and stores
  • references/directory-layout.md
    — 完整目录结构、文件命名和桶导出规范
  • references/patterns.md
    — API层、查询选项、hooks、变更、乐观更新、context和存储的带注释代码模式