typescript 在分配对象成员时避免使用非空Assert运算符

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

寻找一种实用的方法来设置或分配对象成员给其他对象,避免使用非空Assert操作符“!“。下面的例子假设formData可以是任意的JS对象。
some.component.ts

export class SomeComponent {
  someModel: SomeModel; 
constructor( ) {  
  this.someModel.set(formData);
 }
}

字符串
some-model.model.ts

export class SomeModel {
  field1: string;
  field2: string;

  constructor({ field1 = "", field2 = "" } = {}) { 
    this.field1 = field1;
    this.field2 = field2;
  }
  setMembers({field1, field2}: {field1?: string | null, field2: string | null} = {}){
    this.field1 = field1 !== null && field1 !== undefined ? field1 : this.field1;
    this.field2 = field2 !== null && field2 !== undefined ? field2 : this.field2;
  }
}


有没有更短的方法来定义setMembers,使setMembers只分配不为null且未定义的参数?我想要类似下面的通用方法,其中setMembers可重用于其他模型,并且与参数的数量无关:

setMembers(data: Partial<Model>): void {
    ModelHelper.set(this, data);
  }


model/model-helper.ts

export class ModelHelper {
  static set<T>(model: T, data: Partial<T>): void {
    for (const key of Object.keys(data) as Array<keyof T>) {
      if (data[key] !== null && data[key] !== undefined) {
         model[key] = data[key] as T[keyof T];
      }
    }
  }
}


但我遇到了“类型”字符串|零|undefined'不能赋值给类型'string|“不确定”的问题再次出现。
所以我必须重新定义签名为:

interface IModel {
  [key: string]: any;
}
setMembers(data: Partial<IModel>): void {
    ModelHelper.set(this, data);
  }

42fyovps

42fyovps1#

您可以编写一个工厂函数,它接受一组键名,并生成一个适当的setMembers()方法,该方法的行为符合您的要求。它可以看起来像这样:

function makeSetter<K extends PropertyKey>(...keys: K[]) {
  return function <T extends Record<K, any>>(
    this: T, data: { [P in K]?: T[P] | null | undefined } = {}) {
    keys.forEach(k => this[k] = data[k] ?? this[k]);
  }
}

字符串
返回的方法使用this参数,只允许在属性与keys中的键匹配的对象上调用它。它接受一个可选的data参数。data参数的属性也是可选的,它们的类型应该与调用该方法的对象上的相应属性的类型相同,除了它们也可以是nullundefined
该实现使用the nullish coalescing operator ( ?? ),以便复制data参数的属性,除非它是nullundefined
使用makeSetter()的最简单方法如下所示:

class SomeModel {
  field1: string = "";
  field2: string = "";
  constructor() { }
  setMembers = makeSetter("field1", "field2");
}

const m = new SomeModel();
m.setMembers({ field1: "abc" });
/* (property) SomeModel.setMembers: <SomeModel>(this: SomeModel, data?: {
  field1?: string | null | undefined;
  field2?: string | null | undefined;
}) => void */


因此,当构建SomeModel示例时,setMembers属性将使用makeSetter("field1", "field2")的结果进行初始化,这是SomeModel所需的setter方法。
当然,这最终导致对每个SomeModel示例都不必要地运行makeSetter。你可以只做其中一个:

const setModelMembers = makeSetter("field1", "field2");
class SomeModel {
  field1: string = "";
  field2: string = "";
  constructor() { }
  setMembers = setModelMembers;
}


即使这样,也不必要地为SomeModel的每个示例创建setMembers示例属性,而理想情况下它应该是原型上的一个方法。这是可能的,但你需要告诉TypeScript期望这样的事情:

class SomeModel {
  field1: string = "";
  field2: string = "";
  constructor() { }
  declare setMembers: typeof setModelMembers;
}
SomeModel.prototype.setMembers = setModelMembers;


现在你希望有一些东西可以用于不同的类:

class Foo {
  a = 1; b = ""; c = true;
  setMembers = makeSetter("a", "b", "c");
}
const f = new Foo();
f.setMembers({ a: "", c: false }); // error!
// ----------> ~
// Type 'string' is not assignable to type 'number'.
f.setMembers({ a: 3, b: "x" });
console.log(f) // {a: 3, b: "x", c: false}


Playground链接到代码

相关问题