Typescript泛化类成员初始化

djp7away  于 2023-08-07  发布在  TypeScript
关注(0)|答案(1)|浏览(144)

我想修改博客构造器从“最小的工作示例”下面的方式被描述为“伪代码”。如何通过泛型ModelHelper类初始化类“Blog”的成员,以便我可以将Blog构造函数重写为:
伪代码:

constructor(data: Partial<IModel> = {}) {
  ModelHelper.initMembers(this, data, initialBlogConfig);
}
export class ModelHelper {
  static initMembers<T, U>(model: T, data: Partial<IModel>, initialModel: U): void {
    for (key in initModel) {
      if(data[key] !== null && data[key] !== undefined) {
        model[key] = data[key];
      }else{
        model[key] = initialModel[key]
      }
    }
  }
}

字符串
“最小工作示例”:

interface IBlog {
  title: string;
  content: string;
}
const initialBlogConfig: IBlog = {    
  title: "",
  content: "",  
};
interface IModel {
  [key: string]: any;
}

class Blog implements IBlog {
  title: string;
  content: string;

  constructor({title, content}: Partial<IModel> = {}) {
    this.title = title ?? "";
    this.content = content ?? "";
  }
}

yhxst69z

yhxst69z1#

如果你想写一个helper函数来处理属性初始化的所有方面,而不是使用初始化器或直接在构造函数中分配属性,那么编译器将无法告诉属性已经初始化,你需要使用一个明确的赋值Assert(!)来抑制错误:

class Blog implements IBlog {
    title!: string;
    content!: string;
    constructor(data: NullableProps<IBlog> = {}) {
        ModelHelper.initMembers(this, data, { title: "", content: "" });
    }
}

let blog = new Blog({ title: "test", content: null })
console.log(blog); // {title: "test", content: ""}

字符串
其中NullableProps<T>是以下形式的实用程序类型

type NullableProps<T> = { [K in keyof T]?: T[K] | null };


因此所有属性都是可选的(因此它们可以是missing或undefined)和可空的(因此它们也可以是null)。
如果是这样,那么您可以以任何方式实现initMembers()。这里有一个方法:

const ModelHelper = {
    initMembers<T extends object>(
        model: T, data: NullableProps<T>, initialConfig: T
    ): void {
        Object.assign(model,
            initialConfig,
            Object.fromEntries(
                Object.entries(data).filter(([_, v]) => v != null)
            )
        );
    }
}


如果你真的想这样做,你可以把它写成一个class和一个static方法,但是如果你不打算使用这个类的构造函数,那么这样做就没有必要(就像在Java中一样),也没有特别的用处。
上面的实现首先使用the Object.assign() methodinitialConfig的所有属性复制到model,然后将data的所有非空属性复制到model(通过filterObject.fromEntries()获得的条目并通过Object.fromEntries()转换回来获得)。
如果你愿意,你可以使用for循环,但是你需要小心从initialConfig * 和data * 中获取所有键,以防modelgenericT类型有任何可选属性:

class Example {
    req1!: string;
    req2!: number;
    opt1?: boolean;
    opt2?: string;
    constructor(data: NullableProps<Example> = {}) {
        ModelHelper.initMembers(this, data, { req1: "a", req2: 1, opt1: false })
    }
}
let example = new Example({ opt2: "b" })
console.log(example) // {req1: "a", req2: 1, opt1: false, opt2: "b"}


任何实现都需要确保正确地复制opt1opt2
另一方面,如果你不介意使用字段初始化器来处理initialConfig的默认值,那么你可以放弃Assert,只让initMembers处理部分输入:

class Blog implements IBlog {
    title = "";
    content = "";
    constructor(data: NullableProps<IBlog> = {}) {
        ModelHelper.initMembers(this, data);
    }
}

let blog = new Blog({ title: "test", content: null });
console.log(blog); // {title: "test", content: ""}

const ModelHelper = {
    initMembers<T extends object>(
        model: T, data: NullableProps<T>
    ): void {
        Object.assign(model,
            Object.fromEntries(
                Object.entries(data).filter(([_, v]) => v != null)
            )
        );
    }
}


使用for循环更容易做到这一点,因为只需要担心data参数,所以你只需要迭代它的键:

const ModelHelper = {
    initMembers<T extends object>(
        model: T, data: NullableProps<T>
    ): void {
        for (const [k, v] of Object.entries(data) as Array<[keyof T, T[keyof T]]>) {
            if (v != null) model[k] = v;
        }
    }
}


Playground链接到代码

相关问题