Communitygithub.com

caseycme/skills - 重构技能

引导系统化重构,使功能与既定模式对齐,消除重复并提高代码复用。包含DataGrid/pattern-library、类型系统、fetch分页、view-model与override-prop指导以及MUI sx-first布局(避免已弃用的Box样式简写属性)。适用于重构功能、合并重复代码、提取共享工具或跨类似功能对齐模式时使用。

skills - 重构技能 是什么?

这是一个专注于前端代码重构的AI助手技能,旨在帮助开发者系统地将功能代码对齐到既定的架构模式,消除重复并提升代码复用性。该技能详细定义了重构前的分析步骤,包括识别类似功能、发现重复代码模式(组件、hooks、工具函数、样式、常量、类型)以及检查现有的共享工具库。对于重构过程,技能分为八个逐步执行步骤:提取共享代码、合并组件、对齐加载模式(使用Next.js App Router约定)、合并样式、验证模式一致性、合并工具函数、清理不必要的注释以及应用代码质量改进(如简化null/undefined检查、使用类型导入、移除不必要的类型断言和nullish合并、提取重复的JSX模式、使用工厂函数处理变体实现)。此外,技能还包含基于Moz Pro UI代码评审反馈的强化规则,涵盖DataGrid详情面板切换、类型系统(避免object/匿名行形状)、Typography与Box的使用指导、组件边界(props与内部状态)、控制流与JSX写法、fetch分页逻辑以及UI演示包装器的优化。该技能适用于Claude Code等AI编程助手,支持TypeScript、React、Next.js和Material-UI技术栈。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/caseycme/skills/tree/HEAD/skills/engineering/refactoring

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

重构技能

在重构功能以对齐既定模式、消除重复并提高代码复用时使用此技能。

对于UI属于pattern-library还是本地组合,请使用design-system技能。

重构前分析

1. 识别类似功能

  • 在代码库中查找具有相似功能的可比功能
  • 比较目录结构、组件模式和工具函数使用情况
  • 查找具有相似功能但实现不同的功能
  • 使用语义搜索查找相关功能:"具有类似表格/数据网格模式的功能"

2. 查找重复机会

搜索重复代码模式:

  • 组件:查找跨功能的类似组件文件(同名或类似功能)
  • Hooks:检查重复的hook逻辑(表格hook、过滤hook、聚合hook)
  • 工具函数:查找重复的工具函数(聚合、格式化、验证、转换)
  • 样式:识别重复的sx属性模式或内联样式
  • 常量:检查重复的常量或配置对象
  • 类型:查找重复的类型定义或接口

3. 检查共享工具函数

在创建新工具函数之前,先验证现有工具:

  • 搜索@/util/目录获取领域特定的工具函数
  • 检查@/util/dates中的日期格式化函数
  • 检查@/util/numbers中的数字格式化函数
  • 检查@/components/中的可复用组件
  • 检查pattern-library中的设计系统组件
  • 在创建新实现之前使用grep/搜索查找现有实现

重构过程

步骤 1:提取共享代码

当发现重复时:

  1. 在适当的@/util/目录中创建共享工具函数:

    // 示例:util/[domain]/[category].ts
    export const sharedFunction = () => { ... };
    export const sharedConstant = { ... };
    
  2. 更新所有功能以从共享位置导入:

    // 功能特定文件 - 重新导出共享内容
    export { sharedFunction, sharedConstant } from "@/util/[domain]/[category]";
    
  3. 从功能目录中删除重复实现

  4. 更新代码库中的所有导入

步骤 2:合并组件

当发现重复组件时:

  1. 识别最完整/功能最丰富的版本(检查属性、功能、测试)
  2. 提取到共享位置(@/components/或与功能无关的位置)
  3. 使属性灵活以支持所有用例(使用联合类型、可选属性或泛型)
  4. 更新所有使用该组件的功能中的导入
  5. 删除重复的组件文件
  6. 验证所有使用是否仍正常工作

步骤 3:对齐加载模式

对于加载状态:

  1. 检查loading.tsx文件 - 应为简单的骨架屏,而非完整页面布局
  2. 移除组件级别的骨架加载器 - 依赖页面级别的loading.tsx处理初始加载
  3. 在重新获取期间:显示现有数据,而非骨架屏(除非用户体验明确需要)
  4. 匹配代码库中类似功能的模式
  5. 使用Next.js App Router约定 - loading.tsx处理初始加载,组件处理重新获取状态

步骤 4:合并样式

对于样式:

  1. 将可复用样式模式提取到共享组件或样式工具函数
  2. 当存在共享组件时移除内联sx属性
  3. 使用pattern-library中的设计系统组件而非自定义样式
  4. 检查可能成为共享工具函数或主题扩展的重复样式对象
  5. 遵循Material-UI模式 - 使用主题值,避免魔法数字

步骤 5:验证模式一致性

确保与既定功能的一致性:

  1. 比较目录结构 - 应遵循类似模式(_components/、_hooks/、_utils/)
  2. 检查hook模式 - 如果功能需求不同,更简单的hook也是可以的
  3. 验证数据流 - 应尽可能匹配类似功能
  4. 审查组件组合 - 应使用类似模式(包装器、组合等)
  5. 检查命名约定 - 遵循既定模式(组件用PascalCase,hook用camelCase)

步骤 6:工具函数合并

对于工具函数:

  1. 首先检查共享工具函数中是否存在该函数(搜索代码库)
  2. 比较实现 - 使用更完整/更健壮的版本
  3. 合并重复项 - 移动到共享位置,更新所有导入
  4. 记录差异 - 如果实现有意不同,记录原因
  5. 保留功能 - 确保合并后的版本处理所有用例

步骤 7:清理注释

重构后,删除不必要的注释:

  1. 删除迁移注释 - 解释代码从何处移动的注释(例如,“isCollecting现在从Table/utils.ts导出”)
  2. 删除显而易见的注释 - 仅重复代码功能的注释(例如,“// 重新导出共享聚合”而代码明显显示重新导出)
  3. 保留解释性注释 - 解释为何这样做的注释(例如,“// 使用setTimeout确保表单状态已更新”)
  4. 保留复杂逻辑注释 - 解释非明显的业务逻辑或边缘情况的注释
  5. 删除TODO/FIXME注释 - 除非是合法的未来工作项

不必要的注释示例:

// ❌ 没必要 - 代码中已明显体现
// 从util重新导出共享聚合
export { latest, totalRange } from "@/util/keywords/gridAggregations";

// ❌ 没必要 - 迁移注释,不再需要
// isCollecting现在从Table/utils.ts导出以避免重复
// 从那里导入:import { isCollecting } from "../KeywordListDetail/Table/utils";

好的注释示例:

// ✅ 好 - 解释为什么
// 使用setTimeout确保CSV处理后表单状态已更新
setTimeout(() => {
  void handleSubmit(onSubmit)();
}, 100);

// ✅ 好 - 解释复杂逻辑
// 关键字在以下情况下更新:
// 1. 其lastUpdated值已更改(最可靠的指示器),或
// 2. 它不再处于收集状态并且我们已轮询足够次数(备用方案)

步骤 8:代码质量改进

在重构代码中应用一致的代码质量模式:

Null/Undefined检查

使用宽松相等简化null和undefined检查:

// ❌ 啰嗦
if (value !== undefined && value !== null) { ... }
if (value !== undefined && value !== null && value !== "") { ... }

// ✅ 简化
if (value != null) { ... }
if (value != null && value !== "") { ... }

注意:使用!= null(宽松相等)在一次检查中同时检查null和undefined。这是惯用的TypeScript/JavaScript,更简洁。

类型专用导入

对于类型专用导入使用import type以提高构建性能并使意图清晰:

// ❌ 为类型专用用法进行运行时导入
import { SomeType } from "app-api/types/domain";

// ✅ 类型专用导入
import type { SomeType } from "app-api/types/domain";

应用于:

  • 来自app-api的类型定义
  • 接口导入
  • 类型专用工具类型
  • 仅用于类型注解的任何导入

移除不必要的类型断言

当TypeScript可以正确推断类型时,移除类型断言:

// ❌ 不必要的断言
const items = data.map((item) => item as SomeType);

// ✅ 让TypeScript推断
const items = data.map((item) => item);

何时保留断言:仅当TypeScript确实无法推断类型,或以TypeScript无法验证的方式缩小类型时。

移除不必要的Nullish合并

当值已可为null或TypeScript推断正确类型时,移除?? null:

// ❌ 没必要 - valueGetter已返回可为null的值
valueGetter: (value, row) => {
  return row.someField ?? null;
};

// ✅ 简化
valueGetter: (value, row) => {
  return row.someField;
};

何时保留:当为了API兼容性而显式将undefined转换为null,或类型系统要求时。

移除未使用的属性

当组件接口和实现中不使用某些属性时,移除它们:

// ❌ 未使用的属性
interface ComponentProps {
  item: Item;
  unusedField: string; // 组件中从未使用
}

// ✅ 干净的接口
interface ComponentProps {
  item: Item;
}

为回调添加显式类型

当TypeScript无法从上下文中推断类型时,为回调参数添加显式类型注解:

// ❌ 隐式的any
const items = ids.map((id) => { ... });

// ✅ 显式类型
const items = ids.map((id: string | number) => { ... });

应用于:数组方法(map、filter、forEach)、事件处理器以及其他上下文不明确类型的回调。

提取重复的JSX模式

将重复的JSX模式提取到可复用组件中:

// ❌ 重复模式
{
  isAggregation ? (
    <Typography
      variant="body2"
      fontWeight="medium"
      fontStyle="italic"
      color="text.secondary"
    >
      {formattedValue}
    </Typography>
  ) : (
    <Typography variant="body2" fontWeight="medium">
      {formattedValue}
    </Typography>
  );
}

// ✅ 提取的组件
<AggregationTypography
  formattedValue={formattedValue}
  isAggregation={isAggregation}
/>;

对变体实现使用工厂函数

当逻辑相似但需要处理不同数据结构时,使用工厂函数:

// ✅ 工厂函数模式
export const createIntentOperators = <T extends { intent?: Intent | Intent[] }>(
  normalizeToArray: (value: T) => Intent[]
) => {
  return {
    // 使用规范化数组的操作符
  };
};

// 在不同功能中使用
const operators1 = createIntentOperators<Feature1Type>((item) => [item.intent]);
const operators2 = createIntentOperators<Feature2Type>(
  (item) => item.intents || []
);

好处:复用核心逻辑,同时允许不同数据结构得到适当处理。

代码审查强化(反复出现的反馈)

当重构涉及网格、仪表板、fetch调用或共享的“行+指标”形状时应用这些规则(与重复的Moz Pro UI PR反馈保持一致)。

DataGrid:详情面板切换

  • 优先使用pattern-library中的DetailPanelToggle 来处理详情面板列的展开/折叠,而不是在自定义单元格中使用临时的IconButton + @mui/icons。
  • 配置可访问性:当单元格应可通过Tab聚焦时,传递buttonTabIndex={0}(参见app/(default)/competitive-research/top-competing-content/_module/components/Table/columns.tsx,SerpFeaturesCell)。
  • 布局/居中:在Box/Stack(或Stack的direction/spacing,当这是既定模式时)上使用**sx。不要用遗留的MUI系统样式属性替代Box上的sx(例如将display、alignItems作为顶级属性)——MUI正弃用那种方式**,转而推荐**sx**。

类型系统:避免object/匿名行形状

  • 不要为brand_definitions、行片段或API相邻列表使用readonly object[],当存在结构类型或可以一次性定义时。
  • 在功能utils模块中定义一个共享的“like”接口(例如BrandDefinitionLike);导出它和一个单一的显示行辅助函数(例如getBrandDefinitionDisplayLine),而不是在单元格中重复使用带类型转换的getBrandName。
  • 在参数类型化后删除冗余的类型转换(as BrandDefinitionLike[])。
  • 当形状是真正的BrandDefinition时,复用来自*_module/utils/dataHelpers的领域辅助函数;仅在提示行载荷不同时使用较宽松的辅助函数。

Typography与Box(pattern-library)

  • 对于文本(空状态、内联“—”、强调的span),优先使用pattern-library中的Typography,并在适当时使用component="span"——而不是用Box来模拟文本。
  • Typography API:在表达语义的地方使用**variant、color、fontWeight、component**(这些未被弃用)。
  • Box / 布局包装器:对于间距、弹性布局、尺寸和主题值,优先使用**sx。避免被动地将样式从sx移动到Box**的“style”简写属性;这与当前MUI指导相冲突。

组件边界:属性与内部状态

  • 从组件内部的data派生布尔值,当父组件不需要它们进行布局或URL状态时(例如,从主要品牌的brand_terms派生“有品牌术语”)。
  • 将仅用于UI的切换保持为内部状态(例如在一个卡片中切换两种图表模式),除非另一个兄弟元素或路由必须驱动相同状态——然后记录为何需要提升。

控制流与JSX

  • 对于加载/空分支,优先使用早期return,而不是在单个return之前立即赋值给变量的大型三元表达式,除非记忆化确实需要一个表达式。
  • 排序比较器:永远不要硬编码与假设的域最大值绑定的魔法哨兵值(例如5);对null/undefined使用**Number.POSITIVE_INFINITY / 显式的“最后排序”**,以便在限制更改时行为保持正确。

Fetch调用与分页

  • 避免使用固定的较小limit(例如100)用于仪表板规模的列表,当后端支持分页且项目可能超过一页时。
  • 循环使用稳定的页面大小,直到一页返回少于限制的行数或零行,或遵循action的文档约定(参见moz-pro-service action文档 / Bruno了解最大limit)。
  • 如果调用方需要计数,一致地展示**total**或派生的总计。

展示包装器:不要重复显而易见的内容

  • 当父级Card已经设置了可见标题时,移除冗余的备选状态字段(例如避免重复与卡片标题匹配的cardContentTitle)。
  • Null检查:如果属性类型为string,不要用与类型矛盾的检查(例如if (ownerName)已足够,不需要if (ownerName != null && ownerName !== undefined && ownerName !== ""))。

跨功能重构策略

当重构跨多个功能时:

  1. 按依赖关系排序 - 先重构独立功能,再重构依赖功能
  2. 使用功能标记 - 如果重构风险高,使用功能标记逐步推出
  3. 逐步提交 - 将大重构拆分为小的、可审查的提交
  4. 协调多个功能 - 更新所有相关功能的导入,以避免构建中断
  5. 添加回退 - 在为共享代码创建新接口时,添加与旧接口的兼容性层

跨功能重构清单

  • 识别所有受影响的文件
  • 更新共享工具函数和常量
  • 更新所有导入
  • 删除重复项
  • 更新测试
  • 添加新的共享测试
  • 在本地完全构建/类型检查
  • 在开发环境中验证所有功能
  • 提交前代码审查

重构后的自动化步骤

考虑以下自动化工具以维持一致性:

  1. ESLint规则 - 添加自定义规则以强制执行模式(例如,优先使用import type)
  2. Prettier - 确保一致的代码格式化
  3. TypeScript严格模式 - 启用严格类型检查
  4. 单元测试覆盖率 - 为新的共享工具函数添加测试
  5. 集成测试 - 为重构的功能添加集成测试

常见重构模式

提取常量

将硬编码的值移动到命名常量中:

// ❌ 硬编码的值
const maxItems = 100;

// ✅ 命名常量
export const MAX_LIST_ITEMS = 100;

创建接口

为复杂对象创建接口:

// ❌ 内联类型
const processItem = (item: { name: string; value: number }) => { ... };

// ✅ 命名接口
interface Item {
  name: string;
  value: number;
}

const processItem = (item: Item) => { ... };

简化条件语句

将复杂的条件逻辑提取到命名函数或变量中:

// ❌ 复杂的条件
if (user.role === "admin" && user.permissions.includes("write") && !user.isSuspended) {
  // 允许写入
}

// ✅ 命名的条件
const canWrite = user.role === "admin" && user.permissions.includes("write") && !user.isSuspended;
if (canWrite) {
  // 允许写入
}

性能考虑

重构时考虑性能影响:

  1. 避免不必要的重新渲染 - 使用React.memo、useMemo、useCallback
  2. 延迟加载 - 使用Next.js动态导入大型组件
  3. 优化数据获取 - 使用SWR或React Query进行缓存和重新验证
  4. 减少包大小 - 使用树摇和动态导入
  5. 监控性能 - 使用React Profiler和运行时间测试

测试策略

重构后,确保测试覆盖率:

  1. 为提取的工具函数添加单元测试
  2. 为重构的组件添加集成测试
  3. 更新现有测试以反映更改
  4. 为共享组件添加快照测试(适当时)
  5. 验证重构功能的行为与原始行为相同

文档

为重构的代码添加文档:

  1. 为共享工具函数添加JSDoc注释
  2. 为共享组件添加PropsInterface文档
  3. 如果适用,更新Storybook
  4. 记录任何重要的架构决策
  5. 为未来重构维护模式指南

此技能旨在与design-system技能、code-review技能和testing技能一起使用,以获得最佳结果。

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

相关技能