toJSON()成为公共接口的一部分但不是toString()

时间:2018-04-02 19:35:59

标签: typescript

我有一个抽象基类,我想要toJSON()toString()的通用实现。对于toString(),我可以这样实现:

public toString() {
  const obj: IDictionary = {};
  this.META.properties.map(p => {
    obj[p.property] = (this as any)[p.property];
  });
  return JSON.stringify(obj);
}

然后,假设一个 Person 对象扩展我的抽象基类,如下所示:

export class Person extends BaseSchema {
  // prettier-ignore
  @property @length(20) public name: string;
  @property public age?: number;
  @property public gender?: "male" | "female" | "other";
  // prettier-ignore
  @property @pushKey public tags?: IDictionary<string>;

  // prettier-ignore
  @ownedBy(Person) @inverse("children") public motherId?: fk;
  // prettier-ignore
  @ownedBy(Person) @inverse("children") public fatherId?: fk;
  @hasMany(Person) public children?: fk[];

  @ownedBy(Company) public employerId?: fk;
}
  

注意:装饰器代码是非令人兴奋的,我将其排除在外,因为我认为它会使事情变得不必要......如果人们对超级感兴趣,可以在here找到所有装饰者

然后我在某处看到了一个输入签名:

 public set<T>(path: string, person: T) { ... }

传递公共方法完全没问题,并且不要求我传递toString方法。但是,当我使用名为toJSON()的方法完全时,它会给我错误:

  

[TS]   类型'{name:string;年龄:数量; ''不能赋值给'Person'类型的参数。     类型'{name:string;'中缺少属性'toJSON';年龄:数量; }“

enter image description here

有谁理解为什么会这样?

1 个答案:

答案 0 :(得分:1)

Typescript有一个结构类型系统,其中类型兼容性由结构兼容性决定。这意味着具有Person类的所有字段和方法的对象文字与类型Person的参数兼容。

如果我们的类只有字段和toString方法,则任何具有必填字段的对象文字都是兼容的,因为toString方法隐式存在于所有对象上。一旦添加其他方法,编译器就会开始抱怨对象文字没有额外的方法。您可以通过定义额外的方法使对象文字再次兼容,但对象文字仍然不是类的实例

class Person{
  name: string;
  toJson() {
    return this.name;
  }
}

let literal: Person = {
  name: '',
  toJson() { return '';}
}
console.log(literal instanceof Person) //false!

您应该定义一个构造函数,该构造函数将类的字段作为参数并将它们分配给类的字段。然后使用new创建类的实例:

type NonMethodKeys<T> = ({[P in keyof T]: T[P] extends Function ? never : P } & { [x: string]: never })[keyof T];  
type RemoveMethods<T> = Pick<T, NonMethodKeys<T>>; 

class Person {
  constructor(data: RemoveMethods<Person>) {
    Object.assign(this, data)
  }
  name: string;
  toJson() {
    return this.name;
  }
}

let aPerson: Person = new Person({
  name: '',
});

console.log(aPerson instanceof Person) //true

或者,如果您希望set方法只占用person的字段,则可以使用RemoveMethods<Person>作为set的类型参数,但我不确定set的作用和如果它关心参数是Person的实际实例,或者它只需要Person

字段的内容
db.set<RemoveMethods<Person>>('url', {
  name:'',
  age:42
}) // should compile but might not work, hard to say without more info on set