在大型前端项目中,下拉选项数据管理是一个常见但容易被忽视的痛点。我们的项目中存在多种格式的选项标识符,如代码格式(OPTION_A1
)和数字格式(100001
),它们在业务逻辑上表示同一个选项实体,但在代码中却被分散管理,导致维护困难和扩展性差。
// 问题:魔法字符串散布在代码中
const getOptionLabel = (optionId: string): string => {
switch (optionId) {
case OptionType.TYPE_A:
case '100001': // 硬编码!
return OptionLabel.TYPE_A;
case OptionType.TYPE_B:
case '200001': // 硬编码!
return OptionLabel.TYPE_B;
default:
return optionId || '--';
}
};
TYPE_A_ID
、TYPE_B_ID
等命名方式不够语义化// 分散在各处的硬编码
export enum OptionType {
TYPE_A = 'OPTION_A1',
TYPE_B = 'OPTION_B1'
}
// 硬编码的字符串常量
const OPTION_ID_CONSTANTS = {
TYPE_A_ID: '100001',
TYPE_B_ID: '200001'
};
// 选项实体配置 - 更具扩展性的数据结构
export const OPTION_ENTITIES = {
// A类型选项的所有标识符
TYPE_A: [
'OPTION_A1', // 代码格式
'100001' // 数字格式
// 未来可以继续添加: '新的A类型ID1', '新的A类型ID2'
],
// B类型选项的所有标识符
TYPE_B: [
'OPTION_B1', // 代码格式
'200001' // 数字格式
// 未来可以继续添加: '新的B类型ID1', '新的B类型ID2'
]
} as const;
// 需要手动维护每个映射关系
export const OPTION_ID_MAPPING = {
OPTION_A1: OptionType.TYPE_A,
'100001': OptionType.TYPE_A,
OPTION_B1: OptionType.TYPE_B,
'200001': OptionType.TYPE_B
} as const;
// 动态生成选项ID映射关系
export const OPTION_ID_MAPPING: Record<string, OptionType> = (() => {
const mapping: Record<string, OptionType> = {};
// A类型选项的所有标识符都映射到 TYPE_A
OPTION_ENTITIES.TYPE_A.forEach((id) => {
mapping[id] = OptionType.TYPE_A;
});
// B类型选项的所有标识符都映射到 TYPE_B
OPTION_ENTITIES.TYPE_B.forEach((id) => {
mapping[id] = OptionType.TYPE_B;
});
return mapping;
})();
export const OPTION_IDS = {
TYPE_B: [OptionType.TYPE_B, '200001'] as const, // 混合使用
TYPE_A: [OptionType.TYPE_A, '100001'] as const // 混合使用
} as const;
export const OPTION_IDS = {
TYPE_B: [OPTION_ENTITIES.TYPE_B[0], OPTION_ENTITIES.TYPE_B[1]] as const,
TYPE_A: [OPTION_ENTITIES.TYPE_A[0], OPTION_ENTITIES.TYPE_A[1]] as const
} as const;
// 优化前:需要在多个地方修改
// 1. 定义新常量
// 2. 更新映射关系
// 3. 更新配置
// 4. 考虑命名问题
// 优化后:只需在一个地方添加
export const OPTION_ENTITIES = {
TYPE_A: [
'OPTION_A1',
'100001',
'新的A类型ID1', // ✅ 直接添加,无需考虑命名
'新的A类型ID2' // ✅ 自动生效
],
TYPE_B: [
'OPTION_B1',
'200001',
'新的B类型ID1' // ✅ 直接添加
]
};
// 优化后:支持新的选项类型
export const OPTION_ENTITIES = {
TYPE_A: [...],
TYPE_B: [...],
TYPE_C: [ // ✅ 新增C类型选项
'OPTION_C1',
'300001'
]
};
OPTION_ENTITIES
// 所有现有代码无需修改
getOptionLabelById('OPTION_A1'); // 'A类型' ✅
getOptionLabelById('100001'); // 'A类型' ✅
getOptionLabelById('OPTION_B1'); // 'B类型' ✅
getOptionLabelById('200001'); // 'B类型' ✅
// Hook 接口保持不变
const { getParams, onOptionChange } = useOptionData(); // ✅
// 可选择性使用的新功能
getAllOptionIds(OptionType.TYPE_A); // 获取所有A类型ID
isOptionType('100001', OptionType.TYPE_A); // 类型检查
getAllFlatOptionIds(); // 获取所有ID的扁平数组
这次优化虽然看似简单,但体现了软件工程中的重要思想:通过合理的抽象和设计,将复杂性封装在内部,对外提供简洁一致的接口。
优化的核心不在于技术的复杂性,而在于对业务场景的深入理解和对未来扩展性的前瞻性思考。一个好的架构设计,应该能够让开发者在面对新需求时感到轻松,而不是恐惧。
通过这次实践,我们不仅解决了当前的问题,更为未来的扩展奠定了坚实的基础。这正是技术优化的真正价值所在。
本文展示了一个典型的前端架构优化案例,希望能为类似场景的优化提供参考和启发。