接受任何对象作为函数中的参数

时间:2021-03-12 16:36:07

标签: typescript

我有什么

我在一个类中有一个方法,我想接受任何对象作为参数,但我不想使用 any,为此我使用 generic 并扩展 {{1} }.

object

使用此实现,class MyClass { saveObject<T extends object>(object: T | null) {} } 规则会抱怨以下错误: @typescript-eslint/ban-types ok,那我就听 Eslint 做如下实现:

Don't use 'object' as a type. The 'object' type is currently hard to use see this issue(https://github.com/microsoft/TypeScript/issues/21732)). Consider using 'Record<string, unknown>' instead, as it allows you to more easily inspect and use the keys.

通过上面的实现,Eslint错误消失了,所以我尝试用随机对象来执行该方法:

class MyClass {
  saveObject<T extends Record<string, unknown>>(object: T | null) {}
}

但是打字稿编译器抛出了一个新错误

anyOtherMethodInMyCode(payment: IPaymentModel | null): void {
    // execute our method  
    this.saveObject(payment);
}

一种选择是在传递给方法的参数中使用 TS2345: Argument of type 'IPaymentModel | null' is not assignable to parameter of type 'Record<string, unknown> | null'.    Type 'IPaymentModel' is not assignable to type 'Record<string, unknown>'.     Index signature is missing in type 'IPaymentModel'. ,如下所示:

Type Assertion

使用上述方法,TS 编译器错误消失了,但在方法执行的所有位置都必须执行此操作(类型断言)并不是最佳选择。

我想要的

我不明白如何在不使用 anyOtherMethodInMyCode(payment: IPaymentModel | null): void { // execute our method with Type Assertion this.saveObject(payment as Record<string, unknown>); } 的情况下接受任何对象作为参数而不会出现此类错误。

1 个答案:

答案 0 :(得分:4)

我不完全同意 typescript-eslint's ban-types rule 的默认配置的前提

<块引用>

避免使用 object 类型,因为目前由于无法断言密钥存在而难以使用。见microsoft/TypeScript#21732

作为提交链接问题的人,我确实理解尝试使用内置类型保护来获取 object 类型的值并对其属性做任何有用的事情是痛苦的。如果能解决这个问题就太好了。但是,类型 objectRecord<string, unknown> 没有的方式表示“TypeScript 中的非原始值”。而且,正如您所注意到的,Record<string, unknown> 有其自身的问题,例如 microsoft/TypeScript#15300。 TypeScript 有很多陷阱和痛点,我认为不建议一概反对一个而支持另一个。

--

对于此特定用例,您可以从 object 切换到 {[k: string]: any} 而不是 {[k: string]: unknown}。如果您进行此更改,将更容易处理 saveObject 中的值:

saveObject(obj: Record<string, any> | null) {
  if (obj === null) {
    console.log("nothing to save");
    return;
  }
  if (obj.format === "json") {
    // do something
  }
}

(我已经更改了您的示例,使其不是通用的;这对于您的实际用例可能很重要,但作为代码示例,如果在任何地方都没有使用该通用性,则将函数设为通用是没有意义的. 带有类型签名 <T extends U>(x: T)=>void 的函数经常可以用 (x: U)=>void 替换而不会产生不良影响)

这种增加的易用性并不是真正的类型安全,因为拥有 any 类型的属性类似于关闭类型检查。但是 TypeScript 中有特殊的大小写,它允许将任何对象分配给 {[k: string]: any} 而不是 {[k: string]: unknown}。后一种类型禁止任何没有显式 index signatureinterface 类型(参见 microsoft/TypeScript#15300),而前者与 object 非常相似并且没有这个限制(参见 {{ 3}}).

如果这对您有用(并且您没有使用 linting 来禁止 any),那么您会发现事情会更好:

anyOtherMethodInMyCode(payment: IPaymentModel | null): void {
  this.saveObject(payment); // okay
}
otherTests(): void {
  this.saveObject("not an object"); // error!
  this.saveObject(() => 3); // okay, a function is an object
}

我仍然会说,除非 objectsaveObject() 的实现中给您一些特定的问题,否则为那一行禁用 linter 并使用 object 是合理的。它比 Record 更能表达“任何非原始”。根据 linter,不使用 object 的假设原因是它很难使用。那是真实的;但是供应很容易,如果您希望在比实施更多的地方调用this.saveObject(),我宁愿在实现中做一件烦人的事情,而不是在每个调用站点上做很多烦人的事情。

microsoft/TypeScript#41746