首页
This commit is contained in:
121
.trellis/spec/frontend/component-guidelines.md
Normal file
121
.trellis/spec/frontend/component-guidelines.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# Component Guidelines
|
||||
|
||||
> This document defines the standards for creating, naming, and organizing React components in this project.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
We follow a strict functional component pattern with a focus on style isolation, type safety, and clean logic separation. Every component should be modular, self-contained, and well-documented.
|
||||
|
||||
---
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
### 1. File and Folder Naming
|
||||
- Use **kebab-case** (`xx-xx`) for component folders and files.
|
||||
- If the name is short, a single word is acceptable (e.g., `button.tsx`).
|
||||
- Example: `search-bar/reveal.tsx`, `user-list-item.tsx`.
|
||||
|
||||
### 2. Function Naming
|
||||
- Use **PascalCase** (`XxXx`) for the component function name.
|
||||
- Example: `const SearchBar = () => { ... }`.
|
||||
|
||||
---
|
||||
|
||||
## Component Structure
|
||||
|
||||
### 1. Functional Implementation
|
||||
All components must be written as **Functional Components**.
|
||||
|
||||
### 2. Styling & Isolation
|
||||
- **File**: Create an `index.scss` file in the same directory as the component.
|
||||
- **Root Class**: The root `div` (or container) of the component must have a **unique class name**.
|
||||
- **Isolation**: Use **SCSS nesting** with the unique root class as the parent to ensure style isolation.
|
||||
|
||||
```tsx
|
||||
// reveal.tsx
|
||||
import './index.scss';
|
||||
|
||||
export const UserCard = () => {
|
||||
return (
|
||||
<div className="user-card-wrapper">
|
||||
<h1 className="title">User Info</h1>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
```
|
||||
```scss
|
||||
// index.scss
|
||||
.user-card-wrapper {
|
||||
// Nested styles for isolation
|
||||
.title {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
## Props and Type Safety
|
||||
- Props Constraint: Every component with props must define a type/interface to constrain them.
|
||||
- Type Location:
|
||||
- Single Interface: If the component only needs one interface (usually the Props), define it at the top of the component file, directly below the import statements.
|
||||
- Multiple Interfaces: If there are 2 or more interfaces/types, create a types.ts file in the same directory. Move all types, including the Props interface, into this file.
|
||||
## Logic and Data Management
|
||||
### 1. Static Data Extraction
|
||||
If a component requires a large amount of static data (e.g., "dead" data for rendering, initialization configs, or constants), do not define them inside the component file.
|
||||
- Move the data to a separate .ts file in the same directory.
|
||||
- Import it using: import { CONFIG_DATA } from "./config.ts".
|
||||
### 2.Commenting Standards
|
||||
**Every method, property, interface, and complex logic block MUST be documented.**
|
||||
|
||||
- **Language Requirement**: All comments inside the code (JSDoc and internal) **MUST be written in Chinese**.
|
||||
- **Public API/Props/Interfaces**: Use JSDoc style `/** ... */` **mandatory** for every interface definition and **every single property** within that interface.
|
||||
- **Methods & Functions**: Every function (exported or internal) **must** have a `/** ... */` comment explaining its purpose, parameters, and return value.
|
||||
- **Internal Logic**: Use double-slash `//` for step-by-step explanations inside function bodies.
|
||||
```tsx
|
||||
/**
|
||||
* 用户数据
|
||||
*/
|
||||
interface UserDetail {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface UserCardProps {
|
||||
/** 用户数据详情 */
|
||||
data: UserDetail;
|
||||
}
|
||||
|
||||
export const UserCard = ({ data }: UserCardProps) => {
|
||||
/**
|
||||
*点击用户中心
|
||||
*/
|
||||
const handleProfileClick = () => {
|
||||
// 检查用户id
|
||||
if (data.id) {
|
||||
console.log('Navigating...');
|
||||
}
|
||||
};
|
||||
|
||||
return <div className="user-card" onClick={handleProfileClick}>...</div>;
|
||||
};
|
||||
```
|
||||
|
||||
## Summary Checklist
|
||||
|
||||
- [ ] Folder/File name is kebab-case.
|
||||
|
||||
- [ ] Function name is PascalCase.
|
||||
|
||||
- [ ] Props are constrained by a type/interface.
|
||||
|
||||
- [ ] Styles are in index.scss using a unique root class and nesting.
|
||||
|
||||
- [ ] Large static data is moved to a separate .ts file.
|
||||
|
||||
- [ ] Proper use of /** */ for definitions and // for logic.
|
||||
|
||||
- [ ] Types are organized: in-file if single, types.ts if multiple.
|
||||
- [ ] **CRITICAL**: Every interface, property, and method must have **Chinese comments** using JSDoc (`/** */`) style.
|
||||
88
.trellis/spec/frontend/directory-structure.md
Normal file
88
.trellis/spec/frontend/directory-structure.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Directory Structure
|
||||
|
||||
> How frontend code is organized in this project, optimized for Next.js and React.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This project follows a **modular and concern-separated** structure. We prioritize **colocation** (keeping related code close to where it's used) while maintaining a clean global `src/` directory for shared resources.
|
||||
|
||||
- **Next.js App Router**: Used for routing and layouts.
|
||||
- **Component-Driven**: UI is split into atomic `common` components and module-specific `business` components.
|
||||
- **Strict Naming**: Enforces consistency across the codebase.
|
||||
|
||||
---
|
||||
|
||||
## Directory Layout
|
||||
|
||||
```text
|
||||
src/
|
||||
├── app/ # Next.js App Router (Routes, Layouts, Pages)
|
||||
│ ├── (auth)/ # Route group (e.g., login, register)
|
||||
│ ├── dashboard/ # Dashboard module
|
||||
│ │ ├── components/ # Local components (kebab-case)
|
||||
│ │ ├── hooks/ # Local hooks (camelCase)
|
||||
│ │ ├── services/ # Local API calls/logic
|
||||
│ │ ├── page.tsx # Page entry
|
||||
│ │ └── layout.tsx # Nested layout
|
||||
│ ├── globals.css # Global styles entry
|
||||
│ └── layout.tsx # Root layout
|
||||
├── api/ # Global Axios instances and shared API clients
|
||||
├── assets/ # Static assets (images, fonts, SVGs)
|
||||
├── components/ # Global reusable UI components
|
||||
│ ├── common/ # Atomic UI components (e.g., /my-button)
|
||||
│ └── business/ # Shared business components (e.g., /user-select)
|
||||
├── hooks/ # Global custom hooks (e.g., useAuth.ts)
|
||||
├── layouts/ # Shared layout wrappers (non-routing layouts)
|
||||
├── store/ # State management (Zustand stores)
|
||||
├── styles/ # Global style configurations (Tailwind, Mixins)
|
||||
├── types/ # Global TypeScript interfaces and entities
|
||||
└── utils/ # Global utility/helper functions
|
||||
```
|
||||
## Module Organization
|
||||
### Global vs. Local
|
||||
- Global: If a component, hook, or util is used by 3 or more routes, move it to the root src/components/, src/hooks/, or src/utils/.
|
||||
- Local: If it's specific to a single feature, keep it inside that feature's directory in src/app/....
|
||||
### The app/ Directory (Routing)
|
||||
- We use Route Groups (folders with parentheses, e.g., (auth)) to organize routes without affecting the URL.
|
||||
- Each route folder should ideally contain its own page.tsx, and if complex, its own components/ sub-folder.
|
||||
|
||||
## Naming Conventions
|
||||
### Folders
|
||||
- Component Folders: Use kebab-case (e.g., src/components/common/search-bar/).
|
||||
- Other Folders: Use camelCase (e.g., src/hooks/, src/utils/, src/api/).
|
||||
- Next.js Routing: Follows Next.js standards (kebab-case for URL segments).
|
||||
### Files
|
||||
- React Components: reveal.tsx inside the component folder.
|
||||
- Hooks: use prefix with camelCase (e.g., useDebounce.ts).
|
||||
- Styles: *.module.scss or *.css (if using Scoped CSS).
|
||||
- Types: *.types.ts or types.ts.
|
||||
|
||||
## Examples
|
||||
### A Standard Business Component
|
||||
```text
|
||||
src/components/business/user-profile-card/
|
||||
├── reveal.tsx # Component logic
|
||||
├── user-avatar.tsx # Sub-component
|
||||
├── types.ts # Local types
|
||||
└── style.module.scss # Local styles
|
||||
```
|
||||
|
||||
### A Route Module
|
||||
```text
|
||||
src/app/dashboard/settings/
|
||||
├── components/ # Components only used in Settings
|
||||
│ └── profile-form/
|
||||
├── hooks/ # Hooks only used in Settings
|
||||
│ └── useSettings.ts
|
||||
├── page.tsx # Entry point for /dashboard/settings
|
||||
└── layout.tsx # Layout for settings pages
|
||||
```
|
||||
|
||||
## Path Aliases
|
||||
To avoid deep nesting like ../../../../components, use the following aliases:
|
||||
- @/* -> src/*
|
||||
- @api/* -> src/api/*
|
||||
- @comp/* -> src/components/*
|
||||
- @hooks/* -> src/hooks/*
|
||||
87
.trellis/spec/frontend/hook-guidelines.md
Normal file
87
.trellis/spec/frontend/hook-guidelines.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Hook Guidelines
|
||||
|
||||
> This document defines the standards for creating and using custom hooks to ensure logic reuse, readability, and separation of concerns.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
In this project, Hooks are the primary way to manage side effects and encapsulate business logic. We use hooks to keep components focused solely on the UI, while the "how it works" is abstracted away.
|
||||
|
||||
---
|
||||
|
||||
## Custom Hook Patterns
|
||||
|
||||
### 1. Complex Logic Extraction (Business Hooks)
|
||||
When a feature involves complex states or multiple related methods, extract them into a custom hook. A component should ideally only "call" logic, not define it.
|
||||
|
||||
- **Example (Comment Feature)**: Instead of putting the comment list, the loading state, and the `handleSubmit` function inside the component, move them to `useComments`.
|
||||
- **Benefit**: This makes the component code much cleaner and allows the logic to be tested or reused independently.
|
||||
|
||||
```tsx
|
||||
// use-comments.ts
|
||||
export const useComments = (articleId: string) => {
|
||||
const [comments, setComments] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const postComment = async (content: string) => {
|
||||
// Logic for posting a comment...
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal logic to fetch comments
|
||||
*/
|
||||
const fetchComments = async () => {
|
||||
// Logic for fetching...
|
||||
};
|
||||
|
||||
return { comments, loading, postComment, refresh: fetchComments };
|
||||
};
|
||||
```
|
||||
### 2.Utility Hooks (Shared Logic)
|
||||
Utility hooks are for common browser interactions or state monitoring that are not tied to a specific business feature.
|
||||
- `State Monitoring`: Hooks that trigger actions when a specific state changes.
|
||||
- `Event Listeners`: Hooks for window resizing, scroll detection, or clicking outside an element.
|
||||
- `Example`: useWindowSize, useDebounce, useLocalStorage.
|
||||
|
||||
----
|
||||
## Naming Conventions
|
||||
- Prefix: All hooks must start with the use prefix (e.g., useAuth, useTableData).
|
||||
- File Naming: Use kebab-case for the filename (e.g., use-comment-list.ts).
|
||||
- Function Naming: Use camelCase for the function name (e.g., useCommentList).
|
||||
|
||||
---
|
||||
## Organization
|
||||
- Global Hooks: Place reusable utility hooks in src/hooks/.
|
||||
- Feature Hooks: Place hooks specific to a single page or module within that module's own hooks/ directory (e.g., src/app/dashboard/hooks/).
|
||||
|
||||
---
|
||||
## Best Practices
|
||||
### 1.Return Types
|
||||
For hooks returning multiple values, we prefer Objects over Arrays for better extensibility and clarity.
|
||||
```tsx
|
||||
// ✅ Recommended: Object return (naming is explicit)
|
||||
const { data, loading } = useData();
|
||||
|
||||
// ❌ Avoid: Array return (unless it mimics useState)
|
||||
const [data, loading] = useData();
|
||||
```
|
||||
### 2. Memoization
|
||||
Use useCallback and useMemo within hooks for any functions or complex values that are passed as dependencies to other hooks or as props to memoized components.
|
||||
|
||||
### 3.Commenting
|
||||
Every custom hook should have a JSDoc comment explaining its purpose and the parameters it accepts.
|
||||
```tsx
|
||||
/**
|
||||
* Monitors the scroll position and returns a boolean if it exceeds a threshold.
|
||||
* @param threshold - The scroll pixel value to trigger the change.
|
||||
*/
|
||||
export const useScrollTrigger = (threshold: number) => {
|
||||
// logic...
|
||||
};
|
||||
```
|
||||
|
||||
## Common Mistakes
|
||||
- Heavy Components: Putting API calls and state transitions directly in reveal.tsx instead of a hook.
|
||||
- Dependency Lies: Forgetting to include variables used inside useEffect or useCallback in the dependency array.
|
||||
- Over-abstraction: Creating a custom hook for something that is only 2 lines of code and only used once. Only extract when it adds clarity or reusability.
|
||||
51
.trellis/spec/frontend/index.md
Normal file
51
.trellis/spec/frontend/index.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Frontend Development Guidelines
|
||||
|
||||
> Best practices and coding standards for the Next.js + React project.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This documentation serves as the single source of truth for frontend development within this project. It ensures consistency, maintainability, and high code quality across the team.
|
||||
|
||||
**Core Tech Stack:**
|
||||
- **Framework**: Next.js (App Router)
|
||||
- **Library**: React 18+
|
||||
- **State Management**: Zustand
|
||||
- **Styling**: SCSS (Scoped with unique root classes)
|
||||
- **Type System**: TypeScript
|
||||
|
||||
---
|
||||
|
||||
## Guidelines Index
|
||||
|
||||
| Guide | Description | Status |
|
||||
|-------|-------------|--------|
|
||||
| [Directory Structure](./directory-structure.md) | Module organization, Next.js App Router layout, and naming rules. | ✅ Active |
|
||||
| [Component Guidelines](./component-guidelines.md) | Naming, SCSS isolation, logic separation, and JSDoc standards. | ✅ Active |
|
||||
| [Hook Guidelines](./hook-guidelines.md) | Business logic extraction, utility hooks, and naming conventions. | ✅ Active |
|
||||
| [State Management](./state-management.md) | Usage of useState vs. Context vs. Zustand and persistence rules. | ✅ Active |
|
||||
| [Type Safety](./type-safety.md) | Rules for Interfaces vs. Types, Generic usage, and API typing. | ✅ Active |
|
||||
| [Quality Guidelines](./quality-guidelines.md) | Async patterns, defensive programming, and React best practices. | ✅ Active |
|
||||
|
||||
---
|
||||
|
||||
## How to Use These Guidelines
|
||||
|
||||
1. **New Features**: Before starting a new module, review the [Directory Structure](./directory-structure.md) to ensure correct placement.
|
||||
2. **Code Reviews**: Use these documents as a checklist during PR reviews to ensure all code meets the project standards.
|
||||
3. **Development**: Follow the [Component Guidelines](./component-guidelines.md) for styling and logic extraction to keep the codebase clean.
|
||||
4. **AI Assistance**: Provide these documents to AI coding assistants to help them generate code that matches our project's specific style.
|
||||
|
||||
---
|
||||
|
||||
## Maintenance
|
||||
|
||||
These guidelines are not static. If you encounter a pattern that is more efficient or discover a recurring mistake not covered here:
|
||||
- Discuss the change with the lead developer.
|
||||
- Update the relevant `.md` file with examples.
|
||||
- Notify the team of the update.
|
||||
|
||||
---
|
||||
|
||||
**Language**: All documentation and code comments must be written in **English**.
|
||||
67
.trellis/spec/frontend/quality-guidelines.md
Normal file
67
.trellis/spec/frontend/quality-guidelines.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Quality Guidelines
|
||||
|
||||
> Principles and standards to ensure code maintainability, reliability, and performance.
|
||||
|
||||
---
|
||||
|
||||
## Code Style & Consistency
|
||||
|
||||
### 1. Naming Conventions (React Specific)
|
||||
- **Event Handlers**: Use the `handle` prefix for functions that handle events (e.g., `handleSubmit`, `handleInputChange`).
|
||||
- **Callback Props**: Use the `on` prefix for props that represent events (e.g., `<Child onSuccess={handleSuccess} />`).
|
||||
- **Boolean Variables**: Use prefixes like `is`, `has`, `should` (e.g., `isLoading`, `hasError`, `shouldRender`).
|
||||
|
||||
### 2. Modern JavaScript Features
|
||||
- **Optional Chaining (`?.`)**: Always use optional chaining when accessing properties of potentially null/undefined objects (especially API responses).
|
||||
- **Nullish Coalescing (`??`)**: Use `??` instead of `||` when you specifically want to handle `null` or `undefined` but allow `0` or `""`.
|
||||
- **Destructuring**: Use destructuring for props and objects to keep code concise.
|
||||
|
||||
---
|
||||
|
||||
## Async & Data Handling
|
||||
|
||||
### 1. Asynchronous Patterns
|
||||
- **Async/Await**: Prefer `async/await` over `.then()` for better readability.
|
||||
- **Error Handling**: Every async operation must be wrapped in a `try...catch` block.
|
||||
|
||||
### 2. Waiting & Loading States
|
||||
- **User Feedback**: Every asynchronous action (like an API call) must have an associated **loading state**.
|
||||
- **Preventing Race Conditions**: Ensure that multiple rapid clicks on a "Submit" button are handled (e.g., by disabling the button during `loading`).
|
||||
|
||||
```tsx
|
||||
const handleSubmit = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await api.saveData(formData);
|
||||
// handle success
|
||||
} catch (error) {
|
||||
// handle error (e.g., show a toast)
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
```
|
||||
## React Best Practices
|
||||
### 1.Component Logic
|
||||
- Early Returns: Use early returns in functions and components to avoid deeply nested if statements.
|
||||
- Pure Functions: Keep logic that doesn't depend on React state outside the component function or in a utils file.
|
||||
### 2.Rendering Performance
|
||||
- Key Prop: Never use the array index as a key prop if the list can change (add/remove/reorder). Use unique IDs.
|
||||
- Expensive Calculations: Wrap expensive calculations in useMemo.
|
||||
### 3.Clean JSX
|
||||
- Avoid Inline Logic: If a ternary operator or logical expression in JSX is too complex, extract it into a variable or a helper function.
|
||||
- Fragment Usage: Use <>...</> (Fragments) to avoid unnecessary DOM nodes.
|
||||
---
|
||||
## Defensive Programming
|
||||
- API Robustness: Never assume the backend will return the exact data structure expected. Always provide fallbacks.
|
||||
```ts
|
||||
// ✅ Good: Defensive and clear
|
||||
const userName = data?.user?.profile?.name ?? 'Guest';
|
||||
|
||||
// ❌ Bad: Fragile
|
||||
const userName = data.user.profile.name;
|
||||
```
|
||||
## Maintenance & Documentation
|
||||
- Meaningful Comments: Don't comment what the code is doing (the code should be self-explanatory); comment why a certain non-obvious approach was taken.
|
||||
- Dead Code: Remove all console.log, commented-out code, and unused variables before submitting a Pull Request.
|
||||
- Complexity: If a component exceeds 250 lines, it is a strong signal that it needs to be refactored into smaller sub-components.
|
||||
102
.trellis/spec/frontend/state-management.md
Normal file
102
.trellis/spec/frontend/state-management.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# State Management
|
||||
|
||||
> This document outlines the strategy and standards for managing state within this project, ensuring data flows predictably and efficiently.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
We follow a tiered state management strategy. Decisions on where to store state are based on the **scope of the data** and the **nesting depth** of the components that require it.
|
||||
|
||||
---
|
||||
|
||||
## State Categories & Selection Criteria
|
||||
|
||||
### 1. Local State (`useState`)
|
||||
- **When to use**: When the state is only used within a single component or passed down to immediate children via props.
|
||||
- **Scope**: Private to the component or its direct sub-tree.
|
||||
|
||||
### 2. Module/Scoped State (`Context API`)
|
||||
- **When to use**: When state needs to be accessed by deeply nested components (e.g., Grandparent to Grandchild) within a **single functional module**, but is not needed globally.
|
||||
- **Scope**: Specific to a feature folder or a logical module.
|
||||
|
||||
### 3. Global State (`Zustand`)
|
||||
- **When to use**: When data needs to be shared across **multiple independent modules** or throughout the entire application (e.g., User Auth, Theme, Global Settings).
|
||||
- **Scope**: Application-wide.
|
||||
|
||||
---
|
||||
|
||||
## Persistence Strategy
|
||||
|
||||
To ensure a seamless user experience, we distinguish between data that should survive a page refresh and data that is volatile.
|
||||
|
||||
### 1. Persistent State (LocalStorage)
|
||||
By default, global state that represents user progress or configuration should be synchronized with **LocalStorage**.
|
||||
|
||||
### 2. Volatile State (`temp.ts`)
|
||||
For data that **should not be cached** (e.g., temporary UI toggles, scroll positions, transient navigation info), we use a dedicated store.
|
||||
- **Action**: Create a `temp.ts` file in the store directory.
|
||||
- **Usage**: All non-persistent data must reside here to avoid cluttering the browser's storage.
|
||||
|
||||
---
|
||||
|
||||
## Zustand Implementation Standards
|
||||
|
||||
When creating Zustand stores, follow the naming conventions for state and actions as shown below.
|
||||
|
||||
### Standard Pattern Example
|
||||
```typescript
|
||||
/**
|
||||
* 临时状态存储接口,用于存放不需要持久化的数据
|
||||
*/
|
||||
interface TempStore {
|
||||
/** 页面滚动条距离顶部的距离 */
|
||||
scrollTop: number;
|
||||
/** 当前路由的元信息(标题等) */
|
||||
routeInfo: routeInfo;
|
||||
/** 当前设备是否为移动端 */
|
||||
mobile: boolean;
|
||||
|
||||
/** 修改滚动条位置 */
|
||||
changeScroll: (value: number) => void;
|
||||
/** 更新路由元信息 */
|
||||
changeRouterInfo: (value: routeInfo) => void;
|
||||
/** 设置移动端状态 */
|
||||
setMobile: (value: boolean) => void;
|
||||
}
|
||||
|
||||
const useTempStore = create<TempStore>((set) => ({
|
||||
// 初始状态
|
||||
scrollTop: 0,
|
||||
routeInfo: {
|
||||
title: '首页'
|
||||
},
|
||||
mobile: false,
|
||||
|
||||
// 方法实现
|
||||
changeScroll: (value) => {
|
||||
set(() => ({ scrollTop: value }));
|
||||
},
|
||||
changeRouterInfo: (value) => {
|
||||
set(() => ({ routeInfo: value }));
|
||||
},
|
||||
setMobile: (data) => {
|
||||
set(() => ({ mobile: data }));
|
||||
}
|
||||
}));
|
||||
|
||||
```
|
||||
## Best Practices
|
||||
- Derived State: Whenever possible, calculate values on-the-fly rather than storing them in the state (e.g., don't store isLoggedIn, store the token and derive isLoggedIn = !!token).
|
||||
- Action Naming: Use clear verbs for actions:
|
||||
- changeX: For updating numerical or incremental values.
|
||||
- setX: For overwriting values or booleans.
|
||||
- Selectors: Always use selectors when consuming Zustand stores to prevent unnecessary re-renders:
|
||||
const scrollTop = useTempStore((state) => state.scrollTop);
|
||||
- Context Cleanup: When using Context, ensure the Provider is wrapped at the lowest common ancestor to minimize the re-render scope.
|
||||
- **Mandatory Documentation**: Every store interface and every single state/action property MUST have a JSDoc `/** */` comment written in **Chinese**.
|
||||
## Common Mistakes
|
||||
- **Missing Comments**: Defining a store or property without a `/** */` JSDoc comment.
|
||||
- **Wrong Language**: Writing comments in English. **All code comments must be in Chinese.**
|
||||
- **Globalizing everything**: Don't put state in Zustand just because it's "easier." If it's local to a form, use `useState`.
|
||||
- **Direct State Mutation**: Never try to mutate the state directly. Always use the `set` function provided by the store.
|
||||
148
.trellis/spec/frontend/type-safety.md
Normal file
148
.trellis/spec/frontend/type-safety.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# Type Safety
|
||||
|
||||
> Type safety patterns and conventions for this project.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This project uses TypeScript to improve code reliability and developer productivity. The approach is pragmatic: strict where it prevents common bugs, and flexible where it avoids unnecessary boilerplate.
|
||||
|
||||
---
|
||||
|
||||
## Core Rules
|
||||
### 1. Mandatory Documentation (Comments)
|
||||
**Every** interface, type alias, and enum MUST be documented using JSDoc style (`/** ... */`) comments.
|
||||
|
||||
- **Language**: All comment content **MUST be written in Chinese**.
|
||||
- **Interfaces/Types**: Describe the overall purpose in Chinese.
|
||||
- **Properties/Members**: Describe each field in Chinese, especially if its purpose isn't immediately obvious.
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* 主应用侧边栏的配置项
|
||||
*/
|
||||
interface SidebarConfig {
|
||||
/** 菜单项的唯一标识符 */
|
||||
id: string;
|
||||
/** 在 UI 界面显示的文本标签 */
|
||||
label: string;
|
||||
/** 可选的图标组件名称 */
|
||||
iconName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前交易的状态枚举
|
||||
*/
|
||||
enum TransactionStatus {
|
||||
/** 交易已创建但尚未处理 */
|
||||
Pending = 0,
|
||||
/** 交易已成功完成 */
|
||||
Success = 1,
|
||||
/** 交易失败或被拒绝 */
|
||||
Failed = 2,
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Objects vs. Simple Types
|
||||
|
||||
- Use `interface` for object definitions and component props. This keeps object shapes extensible and produces cleaner error messages.
|
||||
- Use `type` for unions, intersections, and primitive aliases such as status codes or mode toggles.
|
||||
|
||||
```typescript
|
||||
// Use interface for objects
|
||||
interface UserInfo {
|
||||
id: string;
|
||||
userName: string;
|
||||
age: number;
|
||||
}
|
||||
|
||||
// Use type for value restrictions
|
||||
type ActiveStatus = 0 | 1;
|
||||
type ThemeMode = 'light' | 'dark';
|
||||
```
|
||||
|
||||
### 3. API Response Handling
|
||||
|
||||
To maintain development speed and avoid excessive boilerplate for backend-driven data:
|
||||
|
||||
- Use `any` for API response data when the shape is not reused across the app.
|
||||
- You do not need to define complex nested interfaces for every backend entity unless it is reused across many components as a core business model.
|
||||
|
||||
```typescript
|
||||
// ✅ Pragmatic approach for API
|
||||
async function fetchData() {
|
||||
const res = await axios.get<any>('/api/user/list');
|
||||
return res.data; // data is treated as any
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Generics (Basic Usage)
|
||||
|
||||
Use generics primarily for reusable utility functions or shared components to maintain type continuity without hardcoding types.
|
||||
|
||||
```typescript
|
||||
// Example: A generic wrapper for local storage
|
||||
function getStorageData<T>(key: string): T | null {
|
||||
const data = localStorage.getItem(key);
|
||||
return data ? JSON.parse(data) : null;
|
||||
}
|
||||
|
||||
// Usage
|
||||
const settings = getStorageData<UserInfo>('user_settings');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Type Organization
|
||||
|
||||
- Global types: place shared entities and common types in `src/types/` such as `src/types/auth.ts`.
|
||||
- Local types: place feature-specific types in a `types.ts` file within the module directory, such as `src/app/dashboard/types.ts`.
|
||||
- Documentation: Even local types must follow the mandatory comment rule.
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Component Props
|
||||
|
||||
Always define an interface for props so the component contract stays explicit.
|
||||
|
||||
```typescript
|
||||
interface SearchBarProps {
|
||||
placeholder?: string;
|
||||
onSearch: (value: string) => void;
|
||||
status: ActiveStatus;
|
||||
}
|
||||
|
||||
export const SearchBar = ({ placeholder, onSearch }: SearchBarProps) => {
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
### State Management (Zustand)
|
||||
|
||||
Define the store state and actions with an interface.
|
||||
|
||||
```typescript
|
||||
interface AuthState {
|
||||
token: string | null;
|
||||
setToken: (token: string) => void;
|
||||
clearToken: () => void;
|
||||
}
|
||||
|
||||
const useAuthStore = create<AuthState>((set) => ({
|
||||
token: null,
|
||||
setToken: (token) => set({ token }),
|
||||
clearToken: () => set({ token: null }),
|
||||
}));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Forbidden Patterns
|
||||
|
||||
- **Uncommented Types**: Never define an interface, type, or enum without a `/** */` JSDoc comment.
|
||||
- **Non-Chinese Comments**: All code documentation (JSDoc) must be in **Chinese**. Do not use English for comments.
|
||||
- **Over-engineering**: Do not create complex generic types that are hard to read. If a type takes more than 5 minutes to figure out, simplify it.
|
||||
- **Implicit Any in Props**: Never leave component props untyped. Use at least `interface Props { [key: string]: any }` if the structure is unknown.
|
||||
- **Unnecessary Type Assertions**: Avoid `as UnknownType` unless absolutely necessary.
|
||||
Reference in New Issue
Block a user