react-structure
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact Structure
React组件结构
When working with React code in this project, always follow these conventions. The goal is to keep UI rendering cleanly separated from logic, making components easier to test, review, and maintain.
在本项目中处理React代码时,请始终遵循以下约定。目标是将UI渲染与逻辑清晰分离,让组件更易于测试、评审和维护。
Component file structure
组件文件结构
Every component lives in its own folder with this layout:
ComponentName/
├── ComponentName.tsx # UI only — JSX rendering, no business logic
├── useComponentName.ts # Hook — state, handlers, side effects, data fetching
├── type.ts # TypeScript types and interfaces for this component
└── componentSlice.ts # Redux slice (only if this component owns Redux state)每个组件都存放在独立的文件夹中,结构如下:
ComponentName/
├── ComponentName.tsx # 仅负责UI — JSX渲染,无业务逻辑
├── useComponentName.ts # Hook文件 — 状态处理、事件处理器、副作用、数据请求
├── type.ts # 该组件对应的TypeScript类型与接口
└── componentSlice.ts # Redux切片(仅当组件拥有独立Redux状态时创建)What goes where
文件职责划分
ComponentName.tsx (UI file)
- JSX rendering and layout
- MUI component composition and styling (props,
sx)styled() - Calls the custom hook to get all data and handlers
- No ,
useState,useEffect,useSelector, or business logic hereuseDispatch
tsx
// UserList.tsx
import { useUserList } from './useUserList';
import { DataGridPremium } from '@mui/x-data-grid-premium';
export const UserList = () => {
const { rows, columns, loading, handleRowClick } = useUserList();
return (
<DataGridPremium
rows={rows}
columns={columns}
loading={loading}
onRowClick={handleRowClick}
/>
);
};useComponentName.ts (Hook file)
- All React hooks (,
useState,useEffect,useMemo)useCallback - Redux interactions (,
useSelector)useDispatch - Event handlers and business logic
- Data transformation and preparation
- Returns everything the UI file needs as a clean interface
tsx
// useUserList.ts
import { useSelector, useDispatch } from 'react-redux';
import { GridColDef, GridRowParams } from '@mui/x-data-grid-premium';
import { selectUsers, fetchUsers } from './userSlice';
import { User } from './type';
export const useUserList = () => {
const dispatch = useDispatch();
const users = useSelector(selectUsers);
const [loading, setLoading] = useState(true);
useEffect(() => {
dispatch(fetchUsers()).finally(() => setLoading(false));
}, [dispatch]);
const columns: GridColDef<User>[] = useMemo(() => [
{ field: 'name', headerName: 'Name', flex: 1 },
{ field: 'email', headerName: 'Email', flex: 1 },
], []);
const handleRowClick = useCallback((params: GridRowParams<User>) => {
// handle row click
}, []);
return { rows: users, columns, loading, handleRowClick };
};type.ts
- Interfaces and types scoped to this component
- Props types, data models, enum types
tsx
// type.ts
export interface User {
id: string;
name: string;
email: string;
}
export interface UserListProps {
filterByRole?: string;
}componentSlice.ts (only when needed)
- Redux Toolkit slice with state, reducers, async thunks, and selectors
- Only create this file if the component owns its own piece of Redux state
ComponentName.tsx(UI文件)
- JSX渲染与布局
- MUI组件组合与样式(属性、
sx方法)styled() - 调用自定义hook获取所需的所有数据和处理器
- 此处禁止使用、
useState、useEffect、useSelector或任何业务逻辑useDispatch
tsx
// UserList.tsx
import { useUserList } from './useUserList';
import { DataGridPremium } from '@mui/x-data-grid-premium';
export const UserList = () => {
const { rows, columns, loading, handleRowClick } = useUserList();
return (
<DataGridPremium
rows={rows}
columns={columns}
loading={loading}
onRowClick={handleRowClick}
/>
);
};useComponentName.ts(Hook文件)
- 所有React hooks(、
useState、useEffect、useMemo)useCallback - Redux交互(、
useSelector)useDispatch - 事件处理器与业务逻辑
- 数据转换与预处理
- 将UI文件所需的所有内容以清晰的接口形式返回
tsx
// useUserList.ts
import { useSelector, useDispatch } from 'react-redux';
import { GridColDef, GridRowParams } from '@mui/x-data-grid-premium';
import { selectUsers, fetchUsers } from './userSlice';
import { User } from './type';
export const useUserList = () => {
const dispatch = useDispatch();
const users = useSelector(selectUsers);
const [loading, setLoading] = useState(true);
useEffect(() => {
dispatch(fetchUsers()).finally(() => setLoading(false));
}, [dispatch]);
const columns: GridColDef<User>[] = useMemo(() => [
{ field: 'name', headerName: 'Name', flex: 1 },
{ field: 'email', headerName: 'Email', flex: 1 },
], []);
const handleRowClick = useCallback((params: GridRowParams<User>) => {
// handle row click
}, []);
return { rows: users, columns, loading, handleRowClick };
};type.ts
- 该组件专属的接口与类型
- 组件属性类型、数据模型、枚举类型
tsx
// type.ts
export interface User {
id: string;
name: string;
email: string;
}
export interface UserListProps {
filterByRole?: string;
}componentSlice.ts(仅在需要时创建)
- 包含状态、reducers、异步thunks和选择器的Redux Toolkit切片
- 仅当组件拥有独立的Redux状态时才创建此文件
Tech stack
技术栈
- Language: TypeScript (strict — always type props, state, and return values)
- State management: Redux Toolkit (,
createSlice)createAsyncThunk - Styling: MUI (Material UI) — use prop or
sxfor custom stylingstyled() - Tables: Always use (
@mui/x-data-grid-premium) for any table or data grid. Never use plain HTML tables or basic MUIDataGridPremiumcomponents when displaying tabular data.Table
- 语言: TypeScript(严格模式 — 始终为属性、状态和返回值添加类型)
- 状态管理: Redux Toolkit(、
createSlice)createAsyncThunk - 样式: MUI (Material UI) — 使用属性或
sx方法实现自定义样式styled() - 表格: 所有表格或数据网格均需使用(
@mui/x-data-grid-premium)。展示表格数据时,禁止使用纯HTML表格或基础MUIDataGridPremium组件。Table
Planning — always show a flowchart
规划阶段 — 务必展示流程图
Before writing or modifying any code, present a Mermaid flowchart that shows the function/data flow of what you're about to change or create. This helps the user visualize the impact before any code is written.
The flowchart should cover:
- Which files are involved (hook, UI, slice, types)
- How data flows between them (props, dispatch, selectors, hook return values)
- The user actions and event handler chain
Example — adding a delete feature to UserList:
mermaid
flowchart TD
A[User clicks delete button] --> B[UserList.tsx calls handleDelete]
B --> C[useUserList.ts handleDelete]
C --> D[dispatch deleteUser thunk]
D --> E[userSlice.ts async thunk → API call]
E --> F[userSlice reducer updates state]
F --> G[useSelector picks up new state]
G --> H[UserList.tsx re-renders without deleted row]Always show this flowchart and get confirmation before proceeding with implementation.
在编写或修改任何代码之前,请先提供Mermaid流程图,展示即将修改或创建的功能/数据流。这有助于用户在代码编写前直观了解改动影响。
流程图应涵盖:
- 涉及的文件(hook、UI、切片、类型)
- 文件间的数据流向(属性、dispatch、选择器、hook返回值)
- 用户操作与事件处理器链
示例 — 为UserList添加删除功能:
mermaid
flowchart TD
A[用户点击删除按钮] --> B[UserList.tsx调用handleDelete]
B --> C[useUserList.ts中的handleDelete]
C --> D[dispatch deleteUser thunk]
D --> E[userSlice.ts中的异步thunk → API调用]
E --> F[userSlice reducer更新状态]
F --> G[useSelector获取新状态]
G --> H[UserList.tsx重新渲染,移除已删除行]在开始实现前,务必展示此流程图并获得用户确认。
When modifying existing components
修改现有组件时的规范
If you're editing a component that doesn't follow this structure yet, refactor it to match:
- Extract all logic into
useComponentName.ts - Extract types into
type.ts - Leave only JSX rendering in
ComponentName.tsx - If there's inline Redux state, move it to
componentSlice.ts
If the change is very small (e.g., fixing a typo in a label), use judgment — don't force a full refactor for a one-word change. But for any meaningful logic or UI change, apply the structure.
如果编辑的组件尚未遵循此结构,请将其重构为符合规范的形式:
- 将所有逻辑提取到中
useComponentName.ts - 将类型提取到中
type.ts - 仅在中保留JSX渲染代码
ComponentName.tsx - 如果存在内联Redux状态,将其迁移到
componentSlice.ts
如果改动非常小(例如修复标签中的拼写错误),可灵活判断 — 无需为单个单词的改动进行完整重构。但对于任何有意义的逻辑或UI修改,均需遵循此结构。