【问题标题】:TypeScript '{}' typeTypeScript '{}' 类型
【发布时间】:2020-06-15 21:50:47
【问题描述】:

关于 TypeScript {} 类型的问题 - 到目前为止,我认为它的意思是“没有属性的空对象”类型,但最近我偶然发现了禁止使用 {} 类型的 eslint 规则,因为它表示“任何非空值” .打字稿游乐场的快速测试表明这是真的:

let d: {} = {};

d = false;

这段代码没有给出编译器错误,但是当我尝试将null 分配给d 时,确实有错误。所以我的问题是:

  1. TypeScript 中的 {} 类型实际上是什么?它真的代表“任何非空值”(在 TypeScript 文档中找不到确认)吗?

  2. 我应该如何实际键入“没有任何属性的空对象”?

【问题讨论】:

  • 在此处查看此答案 stackoverflow.com/a/36969975/13058340 -“类型对象 {} 表示 0 字段对象。此类型的唯一合法值是空对象:{}。”
  • @PedroFilipe 虽然这似乎回答了这个问题,而且是一个不错的发现,但它并没有解释为什么代码示例编译时没有错误。
  • 当您将 d 重新分配为 false 时,这是一个有效的操作,因为 let 允许您这样做。您实际上是在说 - 使对这个空对象的引用消失并用错误值替换它
  • 我不明白,我明确告诉编译器 d 是 {} 类型,所以它不应该让我分配布尔值。请注意,分配 null 会导致编译器错误。
  • @PedroFilipe 事实并非如此,如果你反其道而行之,那就不行了:let d: boolean = false; d = {};。这将给出一个编译器错误。

标签: typescript


【解决方案1】:
type EmptyObject = Record<never, unknown>

Record&lt;never, never&gt; 也可以,但unknownnever 做的假设更少,所以它更小。换句话说,如果对象没有键,我们就知道它是空的。

【讨论】:

    【解决方案2】:

    这是最新的 Typescript 声明标准。

    let d: Record<string, unknown>; // will default to {} value
    
    d.whatever = 'hello';
    

    【讨论】:

      【解决方案3】:

      TypeScript 常见问题解答已经提到这一点:
      https://github.com/Microsoft/TypeScript/wiki/FAQ#why-are-all-types-assignable-to-empty-interfaces

      {} 类型没有属性,布尔值也一样:没有属性。

      现在看到逻辑了吗?

      实际上可以尝试在布尔值/数字上查找属性。

      这是您期望的代码:

      {}.a;
      'a' in {};
      

      这两个表达式分别是undefinedfalse

      然而,让我们做一些愚蠢的事情,我们将它换成一个数字和一个布尔值:

      0.0.a;
      'a' in 0.0;
      
      true.a;
      'a' in true;
      

      他们仍然给出相同的结果,没有区别。

      因此,它们可以适应{} 的任何位置,因为它们不会抛出,并且它们将返回所有必需属性的 undefined。

      现在拿这些:

      null.a;
      undefined.a;
      'a' in null;
      

      并且您有运行时错误,因此它们无法模仿空对象{}


      由于我上面给出的...不可能得到一个空对象。

      空原型问题尚未解决,因此我们没有此表达式的类型:Object.create(null)

      类型推断仍然会导致同样的问题:

      let x = {};
      

      x被扣为{},无用。

      const x: Record<never, never> = true;
      

      有效,因此对我们来说是失败的。

      这个好像没有解决办法。

      【讨论】:

        【解决方案4】:

        TypeScript 检查对象是否具有其类型声明的所有属性。但是,TypeScript 通常不要求类型声明对象具有的所有属性。

        例如,如果我们声明:

        let person: {name: string}; 
        

        我们可以做

        let joe = {name: 'joe', occupation: 'developer'};
        person = joe; // just fine: joe has everything a person needs
        

        同样,我们可以这样做:

        let obj: {} = joe; // just fine: joe has everything obj needs
        

        也就是说,{} 类型确实可以分配给任何对象,因为它不需要该对象具有任何特定属性。

        这就留下了一个令人费解的问题:falseboolean,如何可能传递给一个对象。 reason 是:

        为了确定类型关系(第 3.11 节)和访问属性(第 4.13 节),Boolean 原始类型表现为具有与全局接口类型“Boolean”相同属性的对象类型。

        也就是说,因为 EcmaScript 会在需要时自动将 boolean 转换为 Boolean,所以 TypeScript 允许在允许 Boolean 时传递 boolean(并且允许 Boolean 是因为它具有所有属性 @ 987654333@需要...)

        所以是的,除了nullundefined 之外的所有内容都可以放入{} 类型的变量中是正确的。这是设计使然。 {} 不需要任何属性,所以任何对象都可以。

        我应该如何实际键入“没有任何属性的空对象”?

        如果您的意思是:这是某个对象,但我不需要它具有任何特定属性(例如,因为您不会访问任何属性,或者将以通用方式访问它们),使用{}object 很好,尽管object 可能会更清楚地传达您的意图。

        如果你的意思是:这个对象永远不会有任何属性......你不能。 TypeScript 无法确保这一点,因为它必须与 JavaScript 互操作,这允许随时向对象添加属性:

        const empty = {};
        
        // somewhere else
        empty['foo'] = 'bar'; // no longer empty :-)
        

        【讨论】:

          【解决方案5】:

          1。 TypeScript 中的 {} 类型实际上是什么?它真的代表“任何非空值”(在 TypeScript 文档中找不到确认)吗?

          如果您执行console.log(typeof(d));,那么您将看到{} 的类型为object。除了这不完全正确,但让我先解释一下对象。首先object 小写o 可以是任何非原始值,而Object 大写O 可以包含任何原始值到Object.prototype

          因此,如果您尝试使用原始值覆盖对象,则会出现错误,因为它不喜欢原始值,尽管 null 也可以像 object 类型一样工作,另一方面,未定义的类型为未定义但这总是可以分配的。

          现在{} 被称为“对象文字”,这实际上是objectObject。这就是为什么原始值和非原始值都可以分配给packt 中提到的对象字面量。

          所以通常任何值都可以分配给对象字面量。

          您可以通过以下stackblitz查看。

          2。我应该如何实际输入“没有任何属性的空对象”?

          可能有几种方法可以做到这一点,我知道的方法是您可以像以前那样实例化它,也可以这样做:let d = {};,因为类型是自动确定的,无需添加类型。

          另一种方法是在已知属性时使用接口定义属性,但通过在属性名称后面添加问号使它们全部可选。 这也使其易于使用,因为您的所有属性都是已知的,并且也可以通过智能感知找到。

          例子:

          export interface User {
              name?: string;
              email?: string;
          }
          
          let user: User = {};
          user.name = "Joe";
          user.email = "joe@hotmail.com";
          

          如果这不能充分回答您的问题,请随时提问!

          Ps:有关对象的更多信息,请查看2ality

          更新 #1

          正如 Paddokt 在 cmets 中提到的,如果您想将对象键入为空对象或仅作为特定对象,上述示例将不起作用,因此需要采用不同的方法。

          如果您希望上面的示例仅是用户或空对象,则必须将用户对象包装在不同的对象中,如下所示:

          export interface optUser {
              user?: User;
          }
          
          export interface User {
              name: string;
              email: string;
          }
          
          let optuser: optUser = {};
          optuser.user = {
            name: "Joe",
            email: "joe@hotmail.com"
          }
          

          这样,您可以拥有一个变量,它可以是一个空对象,也可以是一个包含需要姓名和电子邮件的用户的对象。

          注意:

          知道optuser.user = {}; 不起作用,要么 optuser 有一个用户对象,要么它根本没有一个对象,因为 User 本身在这里不能是一个空对象。

          【讨论】:

          • 例如,如果您想键入函数的结果或输入,则必须显式键入对象。因此,如果它是 MyInterface 或空对象,则需要不同的解决方案。第二种解决方案也不正确,因为 TS 会认为 user: User = { email: 'a@b.com' } 是正确的,而您可能需要这两个属性或一个都没有。
          • 您所说的确实是正确的,如果您想要一个对象是MyInterface 或一个空对象,您将不得不使用不同的解决方案。它将MyInterface 包装在另一个看起来像这样的对象中:export interface optUser { user?: User; } export interface User { name: string; email: string; } 当一个对象这样写时,optUser 将接受一个空对象或一个包含需要两个属性的用户的对象。我会相应地更新答案。
          猜你喜欢
          • 2019-05-30
          • 2019-09-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-01-05
          • 2017-07-29
          • 2021-07-19
          • 2018-07-03
          相关资源
          最近更新 更多