plan-ui-change

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Plan 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:
ComponentActionRender ModeState OwnedLines (est.)
InventoryDashboardCreateInteractiveServerproduct list, filter state~80
StockSummaryBarCreate(inherits)none — receives data~30
InventoryFiltersCreate(inherits)search text, selected category~60
InventoryTableCreate(inherits)sort column, sort direction~50
InventoryRowCreate(inherits)inline-edit mode flag~60
AddProductFormCreate(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 ←── AddProductForm
Rules:
  • 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>
    ,
    Pagination
    ) that accept templates
If a component will be used in more than one page, place it in a
Shared/
or
Components/
folder.
在创建新组件之前,检查项目中是否已有可满足需求的现有组件。寻找:
  • 结构匹配的现有列表项组件
  • 项目中已有的共享筛选/搜索组件
  • 接受模板的通用组件(例如
    DataTable<T>
    Pagination
如果组件将在多个页面中使用,请将其放置在
Shared/
Components/
文件夹中。

Step 5 — Order the Implementation

步骤5 — 确定实现顺序

Build bottom-up — leaf components first, then parents that compose them:
  1. Models/DTOs — define the data shapes
  2. Services — data access, business logic (interface + implementation)
  3. Leaf components — components with no children (InventoryRow, StockSummaryBar)
  4. Container components — components that compose leaves (InventoryTable, InventoryFilters)
  5. Page component — wires everything together, registers routes
  6. Configuration — DI registration, render mode setup
Each component should be independently compilable. Never reference a component that doesn't exist yet.
自底向上构建 — 先构建叶子组件,再构建组合它们的父组件:
  1. 模型/DTO — 定义数据结构
  2. 服务 — 数据访问、业务逻辑(接口 + 实现)
  3. 叶子组件 — 无子组件的组件(InventoryRow、StockSummaryBar)
  4. 容器组件 — 组合叶子组件的组件(InventoryTable、InventoryFilters)
  5. 页面组件 — 将所有内容连接起来,注册路由
  6. 配置 — 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
undefined

Component Plan: [Feature Name]

组件规划:[功能名称]

Component Tree

组件树

[ASCII tree showing parent-child relationships]
[展示父子关系的ASCII树]

Component Table

组件表格

ComponentActionRender ModePurposeEst. Lines
...............
组件操作渲染模式用途预估行数
...............

Data Flow

数据流

[State owner] → [Parameters down] → [EventCallbacks up]
[状态所有者] → [向下传递的参数] → [向上传递的EventCallback]

Implementation Order

实现顺序

  1. [First file to create — why]
  2. [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.
  1. [要创建的第一个文件 — 原因]
  2. [第二个文件 — 原因] ...

输出规划后,**立即按照列出的顺序实现组件**。不要等待批准或询问“是否继续?”——规划是供你遵循的指南,而非需要用户批准的提案。

Anti-Patterns to Avoid

需避免的反模式

Anti-PatternWhy It's WrongCorrect Approach
One page component with 500+ linesImpossible to test, reuse, or maintainDecompose into focused components
Passing 10+ parameters through intermediate componentsParameter drilling obscures intentUse cascading values or a scoped state service
Child component fetching its own data from an APIMultiple components making redundant callsParent owns data, passes via parameters
Inline rendering of list items with complex markupDuplicated logic, no reuse, hard to testExtract item template into its own component
Building everything in one file then "refactoring later"Refactoring rarely happens; the monolith shipsPlan the decomposition upfront
Generic components for one-off usageOver-engineering adds complexityOnly 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
    .razor
    files — never stop at just the plan.
  • 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:
    ProductCard
    not
    ProductRenderer
    ,
    OrderFilters
    not
    FilterHandler
    .
  • 简要规划,然后实现。编写简洁的组件表格和数据流图,然后立即创建
    .razor
    文件 — 永远不要停留在仅规划阶段。
  • 宁多勿大。单个组件拥有明确的单一用途,更易于理解、测试和复用。
  • 先确定状态所有权。在编写获取逻辑之前,确定哪个组件拥有数据。
  • 自底向上构建。先创建叶子组件,以便父组件可以立即引用它们。
  • 根据组件渲染的内容命名,而非内部功能:使用
    ProductCard
    而非
    ProductRenderer
    ,使用
    OrderFilters
    而非
    FilterHandler