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

TypeScript如何将嵌套可选属性的接口数据赋值给带默认值的类实例

嵌套可选接口数据赋值给带默认值类实例的实现方案

问题背景

需要将全字段可选的嵌套接口类型数据,赋值给预设了默认值的类实例,无需逐一枚举判断每个嵌套属性,也不希望通过禁用lint规则实现。

现有定义

接口定义

export interface ICard {
  title?: string;
  date?: Date;
  style?: ICardStyle;
  body?: ICardContent;
  footer?: ICardFooter;
}

export interface ICardContent {
  content?: string;
  style?: ICardStyle;
}

export interface ICardFooter extends ICardContent {
  username?: string;
  url?: string;
}

export interface ICardStylePadding {
  top?: string;
  bottom?: string;
  left?: string;
  right?: string;
}

export interface ICardStyle {
  color?: string;
  background?: string;
  padding?: ICardStylePadding;
}

带默认值的类实现

export class CardContent implements ICardContent {
  content = 'Some dummy text';
  style = new CardStyle();
}

export class CardFooter implements ICardFooter {
  content = 'Some dummy text for footer';
  style = new CardStyle();
  username = 'Default User';
  url = 'https://example.com';
}

export class CardStyle implements ICardStyle {
  color = 'black';
  background = 'white';
  padding = new CardStylePadding();
}

export class CardStylePadding implements ICardStylePadding {
  top = '10px';
  bottom = '10px';
  left = '5px';
  right = '5px';
} 

现有方案的问题

  • 基于展开运算符、Object.assign的方案仅支持浅合并,嵌套属性会被直接覆盖,丢失默认值
  • 逐字段判断的方案可维护性极差,类属性增减时需要同步修改合并逻辑

最优实现方案

推荐采用类构造函数内置合并逻辑的方案,类型安全、易维护,后续属性迭代仅需修改对应类的内部逻辑,上层加载代码无需改动。

步骤1:为每个类添加接收接口配置的构造函数

从最内层的类开始改造:

export class CardStylePadding implements ICardStylePadding {
  top = '10px';
  bottom = '10px';
  left = '5px';
  right = '5px';

  // 新增构造函数
  constructor(config?: ICardStylePadding) {
    if (config) Object.assign(this, config);
  }
}

export class CardStyle implements ICardStyle {
  color = 'black';
  background = 'white';
  padding = new CardStylePadding();

  constructor(config?: ICardStyle) {
    if (!config) return;
    // 合并基础属性
    Object.assign(this, { color: config.color, background: config.background });
    // 单独处理嵌套属性,传入配置实例化
    if (config.padding) this.padding = new CardStylePadding(config.padding);
  }
}

export class CardContent implements ICardContent {
  content = 'Some dummy text';
  style = new CardStyle();

  constructor(config?: ICardContent) {
    if (!config) return;
    Object.assign(this, { content: config.content });
    if (config.style) this.style = new CardStyle(config.style);
  }
}

export class CardFooter implements ICardFooter {
  content = 'Some dummy text for footer';
  style = new CardStyle();
  username = 'Default User';
  url = 'https://example.com';

  constructor(config?: ICardFooter) {
    if (!config) return;
    Object.assign(this, { 
      content: config.content, 
      username: config.username, 
      url: config.url 
    });
    if (config.style) this.style = new CardStyle(config.style);
  }
}

// 最后改造Card类
export class Card implements ICard {
  title = 'Default Title';
  date = new Date();
  style = new CardStyle();
  body = new CardContent();
  footer = new CardFooter();

  constructor(config?: ICard) {
    if (!config) return;
    Object.assign(this, { title: config.title, date: config.date });
    if (config.style) this.style = new CardStyle(config.style);
    if (config.body) this.body = new CardContent(config.body);
    if (config.footer) this.footer = new CardFooter(config.footer);
  }
}

步骤2:简化加载逻辑

改造后加载配置仅需一行代码,完全满足需求:

public LoadConfig(config: ICard): void {
  this.Config = new Card(config);
}

通用备选方案

如果不想修改所有类的构造函数,可以写一个通用的递归深度合并函数,提前配置嵌套属性对应的类映射即可:

// 定义嵌套属性和对应类的映射
const nestedClassMap = new Map([
  ['style', CardStyle],
  ['body', CardContent],
  ['footer', CardFooter],
  ['padding', CardStylePadding]
]);

// 递归深度合并函数
function deepMerge<T extends object>(defaultInstance: T, config: Partial<T>): T {
  const result = { ...defaultInstance };
  for (const [key, value] of Object.entries(config)) {
    if (value === undefined) continue;
    // 如果是需要实例化的嵌套对象,递归合并
    if (nestedClassMap.has(key) && typeof value === 'object' && value !== null) {
      const Clazz = nestedClassMap.get(key)!;
      result[key as keyof T] = deepMerge(new Clazz(), value) as T[keyof T];
    } else {
      result[key as keyof T] = value as T[keyof T];
    }
  }
  return result;
}

// 使用方式
public LoadConfig(config: ICard): void {
  this.Config = deepMerge(new Card(), config);
}

内容的提问来源于stack exchange,提问作者Malcor

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 08:57:04