plan-ui-change
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePlan a Blazor UI Change
规划Blazor UI变更
When asked to build a complex UI feature, plan the component decomposition first, then immediately implement it. A single monolithic page component is almost never the right answer — break the UI into focused, composable components.
当需要构建复杂UI功能时,先规划组件分解,然后立即实现。单一的整块页面组件几乎永远不是正确的解决方案——将UI拆分为专注、可组合的组件。
Planning Workflow
规划工作流
Step 1 — Map the Visual Regions
步骤1 — 绘制视觉区域
Read the request and identify every distinct visual region. Each region that has its own data, behavior, or layout responsibility is a candidate component.
Draw the component tree:
InventoryDashboard (page — owns data, orchestrates layout)
├── StockSummaryBar (read-only stats: total items, low-stock count, value)
├── InventoryFilters (search box, category dropdown, stock-level toggle)
├── InventoryTable (sortable table of products)
│ └── InventoryRow (single product row with inline edit/delete)
└── AddProductForm (slide-out form for new products)Rules for identifying components:
- Distinct responsibility — a region owns its own state or behavior → separate component
- Repeated structure — items in a list, cards in a grid → extract the item template
- Independent interactivity — a section that handles user input separately from its siblings → separate component
- Size — any section that would exceed ~150 lines of markup on its own → split it
阅读需求并识别每个不同的视觉区域。每个拥有独立数据、行为或布局职责的区域都是组件的候选对象。
绘制组件树:
InventoryDashboard (页面 — 管理数据,协调布局)
├── StockSummaryBar (只读统计:总物品数、低库存数量、价值)
├── InventoryFilters (搜索框、分类下拉菜单、库存级别切换器)
├── InventoryTable (可排序的产品表格)
│ └── InventoryRow (带有内联编辑/删除功能的单个产品行)
└── AddProductForm (用于添加新产品的滑出式表单)识别组件的规则:
- 独立职责 — 某个区域拥有自己的状态或行为 → 拆分为独立组件
- 重复结构 — 列表项、网格中的卡片 → 提取项模板
- 独立交互性 — 与同级组件分开处理用户输入的区块 → 拆分为独立组件
- 大小 — 任何单独标记超过约150行的区块 → 拆分
Step 2 — Classify Each Component
步骤2 — 分类每个组件
For every component in the tree, determine:
| Component | Action | Render Mode | State Owned | Lines (est.) |
|---|---|---|---|---|
| InventoryDashboard | Create | InteractiveServer | product list, filter state | ~80 |
| StockSummaryBar | Create | (inherits) | none — receives data | ~30 |
| InventoryFilters | Create | (inherits) | search text, selected category | ~60 |
| InventoryTable | Create | (inherits) | sort column, sort direction | ~50 |
| InventoryRow | Create | (inherits) | inline-edit mode flag | ~60 |
| AddProductForm | Create | (inherits) | form model | ~80 |
A page component that exceeds ~200 lines of combined markup + code is too large. If your estimate puts a single component above that, split further.
针对树中的每个组件,确定以下信息:
| 组件 | 操作 | 渲染模式 | 拥有的状态 | 预估行数 |
|---|---|---|---|---|
| InventoryDashboard | 创建 | InteractiveServer | 产品列表、筛选状态 | ~80 |
| StockSummaryBar | 创建 | (继承) | 无 — 接收数据 | ~30 |
| InventoryFilters | 创建 | (继承) | 搜索文本、选中的分类 | ~60 |
| InventoryTable | 创建 | (继承) | 排序列、排序方向 | ~50 |
| InventoryRow | 创建 | (继承) | 内联编辑模式标记 | ~60 |
| AddProductForm | 创建 | (继承) | 表单模型 | ~80 |
如果页面组件的标记+代码总行数超过约200行,则过于庞大。如果你的预估显示单个组件超过这个行数,继续拆分。
Step 3 — Design Data Flow
步骤3 — 设计数据流
Identify the state owner for each piece of data, then map how it flows:
InventoryDashboard (owns: products[], filters)
│
├─ [Parameter] products ──→ StockSummaryBar (reads aggregate stats)
│
├─ [Parameter] filters ──→ InventoryFilters
│ └─ EventCallback<Filters> OnFiltersChanged ──→ InventoryDashboard
│
├─ [Parameter] filteredProducts ──→ InventoryTable
│ └─ [Parameter] product ──→ InventoryRow
│ ├─ EventCallback<Product> OnSave ──→ InventoryTable ──→ InventoryDashboard
│ └─ EventCallback<Product> OnDelete ──→ InventoryTable ──→ InventoryDashboard
│
└─ EventCallback<Product> OnProductAdded ←── AddProductFormRules:
- Data always flows down through
[Parameter] - Events always flow up through
EventCallback<T> - The page/parent owns the data and passes filtered/transformed views to children
- Children never mutate parameters — they notify the parent via callbacks
- If data must cross more than 2 levels without intermediate components needing it, use a cascading value or a scoped service
识别每段数据的状态所有者,然后绘制数据流:
InventoryDashboard (拥有:products[], filters)
│
├─ [Parameter] products ──→ StockSummaryBar (读取聚合统计数据)
│
├─ [Parameter] filters ──→ InventoryFilters
│ └─ EventCallback<Filters> OnFiltersChanged ──→ InventoryDashboard
│
├─ [Parameter] filteredProducts ──→ InventoryTable
│ └─ [Parameter] product ──→ InventoryRow
│ ├─ EventCallback<Product> OnSave ──→ InventoryTable ──→ InventoryDashboard
│ └─ EventCallback<Product> OnDelete ──→ InventoryTable ──→ InventoryDashboard
│
└─ EventCallback<Product> OnProductAdded ←── AddProductForm规则:
- 数据始终通过向下流动
[Parameter] - 事件始终通过向上流动
EventCallback<T> - 页面/父组件拥有数据,并将过滤/转换后的视图传递给子组件
- 子组件永远不要修改参数 — 它们通过回调通知父组件
- 如果数据需要跨越2个以上层级且中间组件不需要该数据,请使用级联值或作用域服务
Step 4 — Identify Reuse Opportunities
步骤4 — 识别复用机会
Before creating a new component, check if an existing component in the project can serve the purpose. Look for:
- Existing list-item components that match the structure
- Shared filter/search components already in the project
- Generic components (e.g., ,
DataTable<T>) that accept templatesPagination
If a component will be used in more than one page, place it in a or folder.
Shared/Components/在创建新组件之前,检查项目中是否已有可满足需求的现有组件。寻找:
- 结构匹配的现有列表项组件
- 项目中已有的共享筛选/搜索组件
- 接受模板的通用组件(例如、
DataTable<T>)Pagination
如果组件将在多个页面中使用,请将其放置在或文件夹中。
Shared/Components/Step 5 — Order the Implementation
步骤5 — 确定实现顺序
Build bottom-up — leaf components first, then parents that compose them:
- Models/DTOs — define the data shapes
- Services — data access, business logic (interface + implementation)
- Leaf components — components with no children (InventoryRow, StockSummaryBar)
- Container components — components that compose leaves (InventoryTable, InventoryFilters)
- Page component — wires everything together, registers routes
- Configuration — DI registration, render mode setup
Each component should be independently compilable. Never reference a component that doesn't exist yet.
自底向上构建 — 先构建叶子组件,再构建组合它们的父组件:
- 模型/DTO — 定义数据结构
- 服务 — 数据访问、业务逻辑(接口 + 实现)
- 叶子组件 — 无子组件的组件(InventoryRow、StockSummaryBar)
- 容器组件 — 组合叶子组件的组件(InventoryTable、InventoryFilters)
- 页面组件 — 将所有内容连接起来,注册路由
- 配置 — DI注册、渲染模式设置
每个组件都应能独立编译。永远不要引用尚未存在的组件。
Output Format
输出格式
Present the plan briefly, then immediately proceed to implement — never stop at just the plan or ask for confirmation before writing code. The plan is a thinking tool, not a deliverable.
markdown
undefined简要呈现规划,然后立即开始实现 — 永远不要停留在仅规划阶段,也不要在编写代码前请求确认。规划是思考工具,而非交付物。
markdown
undefinedComponent Plan: [Feature Name]
组件规划:[功能名称]
Component Tree
组件树
[ASCII tree showing parent-child relationships]
[展示父子关系的ASCII树]
Component Table
组件表格
| Component | Action | Render Mode | Purpose | Est. Lines |
|---|---|---|---|---|
| ... | ... | ... | ... | ... |
| 组件 | 操作 | 渲染模式 | 用途 | 预估行数 |
|---|---|---|---|---|
| ... | ... | ... | ... | ... |
Data Flow
数据流
[State owner] → [Parameters down] → [EventCallbacks up]
[状态所有者] → [向下传递的参数] → [向上传递的EventCallback]
Implementation Order
实现顺序
- [First file to create — why]
- [Second file — why] ...
After outputting the plan, **immediately begin implementing** the components in the order listed. Do not wait for approval or ask "shall I proceed?" — the plan is a guide for you to follow, not a proposal for the user to approve.- [要创建的第一个文件 — 原因]
- [第二个文件 — 原因] ...
输出规划后,**立即按照列出的顺序实现组件**。不要等待批准或询问“是否继续?”——规划是供你遵循的指南,而非需要用户批准的提案。Anti-Patterns to Avoid
需避免的反模式
| Anti-Pattern | Why It's Wrong | Correct Approach |
|---|---|---|
| One page component with 500+ lines | Impossible to test, reuse, or maintain | Decompose into focused components |
| Passing 10+ parameters through intermediate components | Parameter drilling obscures intent | Use cascading values or a scoped state service |
| Child component fetching its own data from an API | Multiple components making redundant calls | Parent owns data, passes via parameters |
| Inline rendering of list items with complex markup | Duplicated logic, no reuse, hard to test | Extract item template into its own component |
| Building everything in one file then "refactoring later" | Refactoring rarely happens; the monolith ships | Plan the decomposition upfront |
| Generic components for one-off usage | Over-engineering adds complexity | Only extract generics when reuse is proven |
| 反模式 | 错误原因 | 正确做法 |
|---|---|---|
| 单个页面组件超过500行 | 无法测试、复用或维护 | 分解为专注的组件 |
| 通过中间组件传递10个以上参数 | 参数传递会模糊意图 | 使用级联值或作用域状态服务 |
| 子组件从API自行获取数据 | 多个组件会进行冗余调用 | 父组件拥有数据,通过参数传递 |
| 使用复杂标记内联渲染列表项 | 逻辑重复、无法复用、难以测试 | 将项模板提取为独立组件 |
| 在一个文件中构建所有内容然后“稍后重构” | 重构很少发生,最终会交付整块代码 | 提前规划组件分解 |
| 为一次性使用场景构建通用组件 | 过度设计会增加复杂度 | 仅在确认需要复用时才提取通用组件 |
Guidelines
指南
- Plan briefly, then implement. Write a concise component table and data flow map, then immediately create the files — never stop at just the plan.
.razor - Prefer many small components over one large one. A component with a single clear purpose is easier to understand, test, and reuse.
- State ownership is the first decision. Before writing fetch logic, decide which component owns the data.
- Build bottom-up. Create leaf components first so parent components can reference them immediately.
- Name components after what they render, not what they do internally: not
ProductCard,ProductRenderernotOrderFilters.FilterHandler
- 简要规划,然后实现。编写简洁的组件表格和数据流图,然后立即创建文件 — 永远不要停留在仅规划阶段。
.razor - 宁多勿大。单个组件拥有明确的单一用途,更易于理解、测试和复用。
- 先确定状态所有权。在编写获取逻辑之前,确定哪个组件拥有数据。
- 自底向上构建。先创建叶子组件,以便父组件可以立即引用它们。
- 根据组件渲染的内容命名,而非内部功能:使用而非
ProductCard,使用ProductRenderer而非OrderFilters。FilterHandler