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
相关产品推荐
相关产品推荐

