TypeScript中如何动态为类型添加字段并实现自动遍历格式化?
动态格式化系统的TypeScript实现方案
需求说明
要实现一个动态格式化系统,通过TypeScript自动定义带格式化能力的字段类型,最终期望效果如下:
enum ContentFormat { Markdown = 'md', Text = 'txt', } type FormattableContent<P extends keyof any> = // 待实现类型 type Post = FormattableContent<'body' | 'anotherField'>; // 期望Post类型扩展为: type Post = { body: string; bodyOutput: string; anotherField: string; anotherFieldOutput: string; format: ContentFormat; }
现有实现的问题
原实现中FormattableContent的泛型参数设计冗余,导致Post类型无法正确生成;同时格式化方法formatContent存在联合类型无法遍历、类型推断不顺畅的问题:
// 原冗余的类型定义 type FormattableFields<T, K extends keyof T> = { [P in K]: string; }; type FormattableOutputFields<T, K extends keyof T> = { [P in K as `${string & P}Output`]: string; }; type FormattableContent<T, K extends keyof T> = FormattableFields<T, K> & FormattableOutputFields<T, K> & { format: ContentFormat; }; // 原格式化方法的问题:fields为联合类型无法遍历,类型推断不自然 export const formatContent = async <T, K extends keyof T>(content: FormattableContent<T, K>, fields: K) { for (const k of fields) { // 此处fields不可遍历 content[`${k}Output`] = doSomething(content[k]); } }
修正后的类型定义
简化FormattableContent的泛型参数,直接接收需要格式化的字段联合类型,无需额外的T参数:
enum ContentFormat { Markdown = 'md', Text = 'txt', } // 生成原始字段:每个字段对应string类型 type FormattableFields<K extends keyof any> = { [P in K]: string; }; // 生成输出字段:每个原始字段追加Output后缀,类型为string type FormattableOutputFields<K extends keyof any> = { [P in K as `${string & P}Output`]: string; }; // 组合最终类型:原始字段 + 输出字段 + 格式标识 type FormattableContent<K extends keyof any> = FormattableFields<K> & FormattableOutputFields<K> & { format: ContentFormat; }; // 现在可以正确生成Post类型 type Post = FormattableContent<'body' | 'anotherField'>;
格式化方法的实现
调整泛型参数,让TypeScript自动推断需要格式化的字段集合,同时将fields改为数组类型以支持遍历,通过类型断言保证字段访问的安全性:
// 模拟格式化函数,可根据实际需求替换(比如markdown转HTML) const doSomething = (input: string): string => { return input.toUpperCase(); }; export const formatContent = async <K extends keyof any>( content: FormattableContent<K>, fields: K[] ) => { for (const k of fields) { // 类型断言确保Output字段的访问合法 (content as FormattableContent<K>)[`${k}Output` as `${string & K}Output`] = doSomething(content[k]); } }; // 使用示例:TypeScript会自动推断K为'body' | 'anotherField' const post: Post = { body: 'Hello **World**', anotherField: 'test text', bodyOutput: '', anotherFieldOutput: '', format: ContentFormat.Markdown }; // 调用时自动推断类型,无需手动指定泛型 formatContent(post, ['body', 'anotherField']);
如果希望完全自动推断字段而无需传入fields数组,可以通过类型提取所有原始字段,再将其转换为数组字面量:
export const formatAllContent = async <K extends keyof any>(content: FormattableContent<K>) => { // 提取所有原始字段(排除format和带Output后缀的字段) const fields = Object.keys(content).filter(key => !key.endsWith('Output') && key !== 'format') as K[]; for (const k of fields) { (content as FormattableContent<K>)[`${k}Output` as `${string & K}Output`] = doSomething(content[k]); } }; // 使用示例:无需传入字段数组,自动处理所有可格式化字段 formatAllContent(post);
内容的提问来源于stack exchange,提问作者aghidini
相关产品推荐
相关产品推荐

