AgentSkillsCN

Frontend Module

如何构建包含状态、表单与服务的特性模块

SKILL.md
--- frontmatter
description: How to create feature modules with state, forms, and services
category: Implementation
boundary: Frontend (fe/)
version: 2.2

Frontend Module Creation

How to create a new feature module in fe/.

Architecture

DirectoryPurpose
fe/src/core-design/Reusable primitives (buttons, inputs, modals)
fe/src/page/Thin route wrappers
fe/src/components/<module>/Feature modules
fe/src/services/<module>/API layer (fetch + React Query)
fe/src/store/Global Redux store

State Management (3-Tier)

mermaid
flowchart TD
    Q1{Server data?}
    Q1 -->|Yes| TQ[TanStack Query]
    Q1 -->|No| Q2{Multiple features?}
    Q2 -->|Yes| RX[Redux global]
    Q2 -->|No| Q3{One feature?}
    Q3 -->|Yes| ZS[Zustand module store]
    Q3 -->|No| US[React useState]

1. TanStack Query (Server State)

  • Where: services/<module>/useQuery.ts
  • Use for: API data, cache, invalidation

2. Zustand (Module UI State)

  • Where: components/<module>/store.ts
  • Use for: View mode, menu state, form state, selections
  • Rule: Store ids + flags, not heavy objects

3. Redux (Global UI State)

  • Where: store/*.store.ts
  • Use for: Modal stack, app settings, cross-feature state

Steps to Create Module

1. Create Module Directory

Simple module:

code
fe/src/components/<module-name>/
├── store.ts              # Zustand (if needed)
├── MainComponent.tsx     # Main component
├── CreateDialog.tsx      # Create modal
└── utils.ts              # Helpers

Complex module with multi-layer form:

code
fe/src/components/<module-name>/
├── store.ts              # Zustand UI state
├── MainComponent.tsx     # Main component
├── <FeatureName>/        # Sub-feature folder
│   ├── index.tsx         # Entry (owns FormProvider)
│   ├── store.ts          # Sub-feature Zustand store
│   ├── schema.ts         # yup/zod schema + defaults
│   ├── types.ts          # Form types
│   ├── utils.ts          # Helpers
│   └── sections/         # Form sections (use useFormContext)
│       ├── BasicSection.tsx
│       └── AdvancedSection.tsx
└── utils.ts              # Shared helpers

2. Create Service Layer

code
fe/src/services/<module-name>/
├── api.ts         # Low-level fetch calls
└── useQuery.ts    # TanStack Query hooks

api.ts:

typescript
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;

export const createItem = async (request: CreateRequest, storeId?: string) => {
  const response = await fetch(`${API_BASE_URL}/v1/item/CreateItem`, {
    method: "POST",
    headers: { "Content-Type": "application/json", ...(storeId && { "x-store-id": storeId }) },
    credentials: "include",
    body: JSON.stringify(request),
  });
  const data = await response.json();
  if (!response.ok && data.success === false) throw new Error(data.error.message);
  return data;
};

useQuery.ts:

typescript
export const useCreateItem = ({ storeId, onSuccess, onError }) => {
  return useMutation({
    mutationFn: (request) => createItem(request, storeId),
    onSuccess,
    onError,
  });
};

3. Create Module Store (Zustand)

typescript
// components/<module>/store.ts
import { create } from "zustand";

interface ModuleStore {
  viewMode: "grid" | "list";
  activeMenuId: string | null;
  setViewMode: (mode: "grid" | "list") => void;
  setActiveMenuId: (id: string | null) => void;
}

export const useModuleStore = create<ModuleStore>((set) => ({
  viewMode: "grid",
  activeMenuId: null,
  setViewMode: (mode) => set({ viewMode: mode }),
  setActiveMenuId: (id) => set({ activeMenuId: id }),
}));

4. Multi-Layer Form Pattern (FormProvider)

For complex create/edit flows with multiple sections:

Parent (index.tsx) owns useForm + wraps children with FormProvider:

typescript
import { FormProvider, useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import { schema, defaultValues } from "./schema";

export default function CreateFeature() {
  const form = useForm({ defaultValues, resolver: yupResolver(schema) });
  
  return (
    <FormProvider {...form}>
      <BasicSection />
      <AdvancedSection />
    </FormProvider>
  );
}

Child sections use useFormContext (no props drilling):

typescript
import { useFormContext, useWatch } from "react-hook-form";

export function BasicSection() {
  const { register, setValue, control } = useFormContext<FormValues>();
  const watchedField = useWatch({ control, name: "fieldName" });
  // ...
}

schema.ts defines validation + defaults:

typescript
import * as yup from "yup";

export const schema = yup.object().shape({
  name: yup.string().required("Name is required"),
  price: yup.string().required("Price is required"),
});

export const defaultValues = { name: "", price: "" };

5. Create Entry Page

typescript
// page/Module.tsx
import MainModule from "@Jade/components/module-name/MainModule";

const Module = () => <MainModule />;
export default Module;

6. Add Route

typescript
<Route path="/module" element={<Module />} />

Checklist

  • Created components/<module>/
  • Created services/<module>/ (api.ts + useQuery.ts)
  • Created module store if needed
  • Created entry page in page/
  • Added route
  • Used TanStack Query for server state
  • Used Zustand for module UI state
  • Used FormProvider + useFormContext for multi-layer forms
  • Created schema.ts + types.ts for form validation
  • Followed dark mode patterns
  • Used core-design components