第一次打开这个名为LegacyDataTable.js的文件时,我的表情大概凝固了整整十秒。这个诞生于五年前的React组件,已经经历了十几位开发者的迭代,最终演变成一个近2000行的庞然大物。它负责处理公司核心业务数据的展示与交互,但由于历史原因从未被系统性重构,导致每次需求变更都像在走钢丝。
这个组件的特征非常典型:
更棘手的是,这个组件每月还要处理约20万次用户交互,任何改动都可能影响线上业务。接手后的第三周,我终于下定决心对它进行彻底重构。
我首先用madge工具生成了组件依赖关系图,发现这个"巨无霸"组件:
bash复制npx madge --image legacy-graph.svg ./src/components/LegacyDataTable.js
由于缺乏单元测试,我花了三天时间用Jest+Testing Library补全测试用例,重点覆盖:
javascript复制describe('LegacyDataTable', () => {
test('should handle 10k rows without crashing', async () => {
const mockData = generateTestData(10000);
render(<LegacyDataTable data={mockData} />);
await waitFor(() => {
expect(screen.getAllByRole('row').length).toBeGreaterThan(500);
});
});
});
考虑到风险控制,我决定采用分阶段重构:
原组件最严重的问题是业务逻辑与UI渲染深度耦合。我首先使用"提取函数"手法,将核心逻辑抽离:
javascript复制// 重构前:300行的handleDataUpdate方法
handleDataUpdate(rawData) {
// 数据清洗
// 状态计算
// DOM操作
// 事件绑定
}
// 重构后:
// dataProcessor.js
export function normalizeData(rawData) { /* 纯函数 */ }
export function calculateDerivedState(data) { /* 纯函数 */ }
// useDataTable.js
const useDataTable = () => {
const [state, dispatch] = useReducer(reducer, initialState);
// 所有业务逻辑处理
return { processedData, handlers };
}
基于Atomic Design原则,我将原组件拆分为:
code复制DataTable/
├── atoms/
│ ├── TableCell.js
│ ├── SortIndicator.js
├── molecules/
│ ├── TableRow.js
│ ├── FilterControl.js
├── organisms/
│ ├── TableHeader.js
│ ├── Pagination.js
└── DataTable.js
关键技巧:
针对大数据量场景,我实施了三级优化方案:
react-window实现javascript复制<FixedSizeList
height={600}
itemCount={data.length}
itemSize={50}
>
{Row}
</FixedSizeList>
useMemojavascript复制const sortedData = useMemo(() => {
return heavySort(data, sortConfig);
}, [data, sortConfig]);
React.lazyjavascript复制const AdvancedFilter = React.lazy(() => import('./AdvancedFilter'));
| 指标 | 重构前 | 重构后 |
|---|---|---|
| 代码行数 | 1987行 | 主组件243行 |
| 首次加载时间 | 1200ms | 400ms |
| 交互响应延迟 | 300-500ms | <100ms |
| 测试覆盖率 | 12% | 89% |
| 维护成本 | 高(3人日/需求) | 低(0.5人日/需求) |
初期尝试直接用Redx替换内部state,结果发现:
最终方案:混合使用useReducer+Context API,只有真正全局的状态才提升到Redux。
原组件使用传统的CSS-in-JS方案,导致:
解决方案:迁移到styled-components+CSS Variables,配合BEM命名规范。
javascript复制const StyledRow = styled.div`
--row-highlight: ${props => props.theme.highlight};
&:hover {
background: var(--row-highlight);
}
`;
TypeScript迁移过程中,最耗时的部分是:
any类型的历史代码实用技巧:逐步迁移策略:
@ts-nocheck完成主体重构后,我建立了三个长效机制:
代码健康度看板:监控关键指标
重构待办清单:记录技术债务
markdown复制- [ ] 将剩余class组件转为函数式
- [ ] 实现Web Worker处理大数据
- [ ] 完善可视化测试
组件文档化:用Storybook创建活文档
bash复制npx storybook init
这次重构经历让我深刻体会到:好的架构不是设计出来的,而是演进出来的。对于历史项目,激进的重构往往适得其反,而渐进式的改良配合完善的测试防护,才是可持续的演进之道。