TypeScript泛型提取嵌套变体键异常:CVA类构建器类型未展开
解决TypeScript泛型未展开问题,实现CVA风格类构建器
核心问题原因
TypeScript默认会保留泛型的抽象定义,不会自动展开成具体的属性结构,导致VS Code提示里显示的是泛型参数而非{color: string; size: string}这类具体类型。
解决方案步骤
1. 定义类型展开辅助工具
先写一个Prettify类型,强制TypeScript展开泛型为具体对象结构:
type Prettify<T> = { [K in keyof T]: T[K] } & {}
这个类型通过重新映射属性并交叉空对象,能触发TypeScript的类型展开逻辑,让VS Code显示具体的属性键。
2. 正确提取并定义输入类型
假设你的TConfig结构包含variants字段,先定义基础配置类型,再提取输入类型:
// 基础CVA配置类型 type CVAConfig<TVariants extends Record<string, Record<string, string>>> = { variants: TVariants defaultVariants?: Partial<{ [K in keyof TVariants]: keyof TVariants[K] }> } // 从配置中提取的输入类型,用Prettify包裹 type CVAInput<TConfig extends CVAConfig<any>> = Prettify<{ [K in keyof TConfig['variants']]?: keyof TConfig['variants'][K] }>
如果你的variant取值不需要严格枚举,只需要string类型,把keyof TConfig['variants'][K]替换成string即可。
3. 完整示例代码
type Prettify<T> = { [K in keyof T]: T[K] } & {} type CVAConfig<TVariants extends Record<string, Record<string, string>>> = { variants: TVariants defaultVariants?: Partial<{ [K in keyof TVariants]: keyof TVariants[K] }> } function createCVA<TConfig extends CVAConfig<any>>(config: TConfig) { return (input?: CVAInput<TConfig>) => { // 这里实现类名拼接逻辑 const classes = [] if (input) { for (const key in input) { const variantKey = key as keyof typeof input classes.push(config.variants[variantKey][input[variantKey] as keyof typeof config.variants[variantKey]]) } } // 合并默认变体 if (config.defaultVariants) { for (const key in config.defaultVariants) { classes.push(config.variants[key][config.defaultVariants[key] as keyof typeof config.variants[key]]) } } return classes.join(' ') } } // 使用示例 const buttonBuilder = createCVA({ variants: { color: { primary: 'bg-blue-500 text-white', secondary: 'bg-gray-500 text-gray-100' }, size: { sm: 'text-sm px-3 py-1', lg: 'text-lg px-6 py-2' } }, defaultVariants: { size: 'sm' } }) // 此时VS Code会显示buttonBuilder的输入类型为:{ color?: "primary" | "secondary"; size?: "sm" | "lg" }
内容的提问来源于stack exchange,提问作者Cam Parry
相关产品推荐
相关产品推荐

