You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 14:54:19