【问题标题】:Is there any way to target the plain JavaScript object type in TypeScript?有没有办法在 TypeScript 中定位纯 JavaScript 对象类型?
【发布时间】:2017-06-21 00:55:14
【问题描述】:

2021 年更新

有关使用新功能的有效解决方案,请参阅此答案https://stackoverflow.com/a/59647842/1323504


我正在尝试编写一个函数,我想指出它返回 某种 纯 JavaScript 对象。该对象的签名是未知的,目前也不感兴趣,只有它是一个普通对象的事实。我的意思是一个普通对象,它满足例如 jQuery 的 isPlainObject 函数。例如

{ a: 1, b: "b" }

是一个普通的对象,但是

var obj = new MyClass();

不是“普通”对象,因为它的 constructor 不是 Object。 jQuery 在$.isPlainObject 中做了一些更精确的工作,但这超出了问题的范围。

如果我尝试使用Object 类型,那么它也将兼容任何自定义对象,因为它们继承自Object

有没有办法在 TypeScript 中定位“普通对象”类型?

我想要一个 type,例如,它会满足这个要求。

var obj: PlainObject = { a: 1 }; // perfect
var obj2: PlainObject = new MyClass(); // compile-error: not a plain object

用例

我有一种用于服务器端方法的强类型存根,就像这样。这些存根由我的一个代码生成器生成,基于 ASP.NET MVC 控制器。

export class MyController {
  ...
  static GetResult(id: number): JQueryPromise<PlainObject> {
    return $.post("mycontroller/getresult", ...);
  }
  ...
}

现在当我在消费者类中调用它时,我可以做这样的事情。

export class MyViewModelClass {
  ...
  LoadResult(id: number): JQueryPromise<MyControllerResult> { // note the MyControllerResult strong typing here
    return MyController.GetResult(id).then(plainResult => new MyControllerResult(plainResult));
  }
  ...
}

现在假设控制器方法返回JQueryPromise&lt;any&gt;JQueryPromise&lt;Object&gt;。现在还想象一下,我偶然写了done而不是then。现在我有一个隐藏的错误,因为 viewmodel 方法不会返回正确的承诺,但我不会得到一个编译错误。

如果我有这个虚构的PlainObject 类型,我预计会收到一个编译错误,指出PlainObject 不能转换为MyControllerResult,或类似的东西。

【问题讨论】:

  • 最后,这意味着您几乎可以接受any 值,因为Javascript 中的几乎所有内容都是一个对象,您甚至都不关心它的任何特定特征。您的函数的调用者可能会出于自己的目的决定将您想要的对象实现为一个类;生成的对象仍将与您预期的“普通”对象完美兼容,特别是如果您甚至不关心该对象的任何内容。虽然是一个有趣的问题,但我有点看不出它的实用性。
  • 您通常键入提示来强制执行对象的特定特征;您究竟为什么要为特定特征的absence 键入提示?方法是否返回一个类真的不应该,这是一个实现细节。只要该类实例仍然符合预期的行为,在本例中为any,这无关紧要。
  • @ZoltánTamási 事情是这样的:类型用于执行合同。如果您没有特定的合同,那么您就不能期望静态地强制输入。然而,同样,由于MyControllerResult 要求对象具有一些特定的 属性,您应该记录这些属性,然后将其用作您的类型。我看不到 PlainObject 在这里有什么用处。
  • 这样的事情怎么样?typescriptlang.org/play/…
  • @ZoltánTamási 真的不是。您只是说它应该是Object,但这并没有告诉您有关您期望的数据类型的任何信息。请向我解释为什么根据您将收到的数据类型(即您希望对象具有哪些属性)来定义您的合同是一个坏主意。现在看来你只是专注于应用你有缺陷的想法,而不是纠正你的方法。

标签: javascript typescript typescript2.1


【解决方案1】:

在 TypeScript 3.7.2 中测试:

对于一个平面的物体,你可以这样做:

type Primitive =
  | bigint
  | boolean
  | null
  | number
  | string
  | symbol
  | undefined;

type PlainObject = Record<string, Primitive>;

class MyClass {
  //
}

const obj1: PlainObject = { a: 1 }; // Works
const obj2: PlainObject = new MyClass(); // Error

对于嵌套的普通对象:

type Primitive =
  | bigint
  | boolean
  | null
  | number
  | string
  | symbol
  | undefined;

type JSONValue = Primitive | JSONObject | JSONArray;

interface JSONObject {
  [key: string]: JSONValue;
}

interface JSONArray extends Array<JSONValue> { }

const obj3: JSONObject = { a: 1 }; // Works
const obj4: JSONObject = new MyClass(); // Error

const obj5: JSONObject = { a: { b: 1 } }; // Works
const obj6: JSONObject = { a: { b: { c: 1 } } }; // Works
const obj7: JSONObject = { a: { b: { c: { d: 1 } } } }; // Works

代码改编自https://github.com/microsoft/TypeScript/issues/3496#issuecomment-128553540

【讨论】:

  • 感谢您发布此内容,很抱歉这么久没有注意到。这确实是使用最新功能的更好方法。
  • 请注意,symbol 不是 JSON 的一部分。
【解决方案2】:

在我的代码中,我的内容与您的要求类似:

export type PlainObject = { [name: string]: any }
export type PlainObjectOf<T> = { [name: string]: T }

我也有一个类型保护:

export function isPlainObject(obj: any): obj is PlainObject {
    return obj && obj.constructor === Object || false;
}

编辑

好的,我了解您要查找的内容,但很遗憾,这是不可能的。
如果我理解正确,那么这就是你所追求的:

type PlainObject = {
    constructor: ObjectConstructor;
    [name: string]: any
}

问题是在'lib.d.ts'中Object是这样定义的:

interface Object {
    /** The initial value of Object.prototype.constructor is the standard built-in Object constructor. */
    constructor: Function;

    ...
}

然后是这个:

let o: PlainObject = { key: "value" };

有错误的结果:

Type '{ key: string; }' is not assignable to type 'PlainObject'.
  Types of property 'constructor' are incompatible.
    Type 'Function' is not assignable to type 'ObjectConstructor'.
      Property 'getPrototypeOf' is missing in type 'Function'.

【讨论】:

  • 问题是像var obj: {[index:string]: any} = new MyControllerResult()这样的赋值仍然被使用TS2.1的编译器接受。它可能与noImplicitAny 相关,我没有那个选项,我什至无法在当前代码库中启用它。
  • 为什么会有问题? js中的每一个对象都是一个“PlainObject”,一个类实例也是一个普通对象。它类似于做:class A {} 然后class B extends A {}let a: A = new B()
  • 不,一个 plain 对象是简单的,因为它不是“自定义”类的实例。在您的代码中,您正在检查这一点。但不幸的是,类型保护不是类型提示。
  • 是的,正是如此.. 感谢您指出关键。你知道为什么这样定义吗?我的意思是为什么不将Object.constructor 定义为ObjectConstructor
  • 没关系,我想我已经想通了。它必须是 Function 才能允许任何类实例仍然是 Object
【解决方案3】:

你可以尝试递归类型(朴素的解决方案)

type SerializableObject = { [x: string]: SerializableObject | number | string | [] };

不好,不可怕:)

【讨论】:

    猜你喜欢
    • 2021-06-21
    • 2022-01-06
    • 2017-12-28
    • 1970-01-01
    • 2023-03-08
    • 1970-01-01
    • 2020-04-08
    • 2021-12-14
    • 1970-01-01
    相关资源
    最近更新 更多