甘特图里的任务条通常会用条纹填充来区分计划进度与实际进度,条纹的密度决定视觉上的疏密感,粗细则影响辨识度。当你要封装一个通用的甘特图组件时,这些样式属性就需要一套严谨的TypeScript类型定义来约束。很多开发者习惯用any或者一个宽松的接口糊弄过去,结果配置一旦传错,问题要到运行时才暴露。这篇文章就来聊聊如何围绕条纹密度与粗细这两个维度,设计出一套既灵活又有强约束的类型体系。

用字面量联合类型定义条纹密度档位
条纹密度本质上是几个离散的档位,比如稀疏、中等、密集,而不是任意的数字。如果直接定义成number,调用方传个0.5或者100进来,组件内部就很难处理。更好的做法是用字符串字面量联合类型把这些档位枚举出来,编译器就能在书写配置时给出提示,并在写错时直接报错。
type StripeDensity = 'sparse' | 'medium' | 'dense';
interface StripeConfig {
density: StripeDensity;
}档位和实际像素间距的映射关系可以放在一个常量表里,这样类型定义和运行时数值解耦,后续调整视觉参数不用改动类型:
const DENSITY_GAP_MAP: Record<StripeDensity, number> = {
sparse: 12,
medium: 8,
dense: 4,
};注意这里用了Record<StripeDensity, number>,它强制映射表必须覆盖联合类型中的每一个成员。假如将来新增一个'extra-dense'档位却忘了在映射表里补充对应数值,编译阶段就会报错,这比运行时查出一个undefined间距要友好得多。
有些场景下业务方希望直接以像素为单位微调间距,这时可以把类型放宽为档位与数字的组合,但要用注释或命名区分清楚语义,例如density: StripeDensity | number,并在组件内部对数字做范围钳制,避免出现负数或过大的间距破坏渲染。
约束条纹粗细的范围与单位
粗细是一个数值型属性,但它有明确的合理区间。条纹线宽通常是1到6像素,太细在高分屏上看不清,太粗会让任务条显得杂乱。TypeScript本身不支持数值范围的校验,但可以通过品牌类型(branded type)加上构造函数的模式,把校验逻辑收敛到一处:
type StripeWidth = number & { readonly __brand: 'StripeWidth' };
function createStripeWidth(value: number): StripeWidth {
if (value < 1 || value > 6) {
throw new Error(`条纹粗细必须在 1 到 6 之间,当前值:${value}`);
}
return value as StripeWidth;
}这样一来,条纹配置接口中的粗细字段就可以声明为StripeWidth类型,调用方无法直接塞一个普通数字进去,必须经过createStripeWidth这个入口函数,范围校验自然就完成了。如果你的项目不想引入品牌类型的复杂度,也可以退而求其次,在接口文档中约定范围,并在渲染前用工具函数统一钳制,例如Math.min(Math.max(width, 1), 6)。
除了线宽本身,粗细的观感还和渲染引擎有关。用SVG实现时对应stroke-width属性,用Canvas实现时则是lineWidth,两者对非整数宽度的处理略有差异。类型定义层面可以额外增加一个可选的unit字段,取值为'px' | 'rpx'之类的联合,方便跨端场景下做换算。
组合成完整的条纹配置类型并支持扩展
密度与粗细只是条纹样式的两个维度,实际配置里还会有颜色、角度、是否双层叠加等属性。把它们组合成一个接口,并考虑扩展性:
type StripeDirection = 'diagonal' | 'horizontal' | 'vertical';
interface BaseStripe {
width: StripeWidth;
direction: StripeDirection;
color: string;
}
interface PresetStripe extends BaseStripe {
mode: 'preset';
density: StripeDensity;
}
interface CustomStripe extends BaseStripe {
mode: 'custom';
gap: number;
}
type TaskStripe = PresetStripe | CustomStripe;这里用到了判别联合(discriminated union),mode字段作为判别式。当调用方选择'preset'模式时,TypeScript会自动收窄类型,提示必须提供density;选择'custom'模式时则提示提供gap。这比把所有属性都设为可选要安全得多,避免了既传密度又传间距的歧义配置。
在组件内部处理这个联合类型时,类型收窄同样能发挥作用:
function resolveGap(stripe: TaskStripe): number {
if (stripe.mode === 'preset') {
return DENSITY_GAP_MAP[stripe.density];
}
return stripe.gap;
}最后把条纹配置挂到甘特图任务条的Props类型上,任务数据与样式分离,结构会更清晰:
interface GanttTask<T = unknown> {
id: string;
name: string;
start: Date;
end: Date;
progress: number;
stripe?: TaskStripe;
meta?: T;
}
interface GanttChartProps<T> {
tasks: GanttTask<T>[];
rowHeight?: number;
}泛型参数T让业务方可以为任务附加自定义元数据而不破坏类型推断。整体来看,这套定义的核心思路是:离散属性用字面量联合,连续属性用品牌类型约束范围,互斥的配置形态用判别联合表达。掌握这几个模式后,无论是甘特图还是其他需要精细样式配置的图表组件,都能写出让调用方用起来舒服、改起来放心的类型定义。
TypeScript甘特图条纹样式修改时间:2026-09-16 00:34:38