【问题标题】:How to access object properties of possible empty object in Typescript?如何在 Typescript 中访问可能的空对象的对象属性?
【发布时间】:2021-08-23 15:58:40
【问题描述】:

我有 2 个类似的设置,我希望它们产生相同的结果,但第二个会引发 Typescript 错误:

第一:

interface ISample {
  requiredProp: string
  optionalProp?: string
}

const sampleObj: ISample = { requiredProp: "hello" }

const { requiredProp, optionalProp = "default" } = sampleObj

console.log(requiredProp, optionalProp)

// Expected Output: "hello", "default"
// Actual Output:   "hello", "default"

第二:

interface ISample {
  requiredProp: string
  optionalProp?: string
}

const sampleObj: ISample | {} = { requiredProp: "hello" } // object can now be empty

const { requiredProp = "default", optionalProp = "default" } = sampleObj

console.log(requiredProp, optionalProp)

// Expected Output: "hello", "default"
// Actual Output:   Error: Property 'requiredProp' does not exist on type '{} | ISample'. Property 'optionalProp' does not exist on type '{} | ISample'.

是的,谢谢 Typescript,我知道这些道具可能不存在于那个空对象上这就是为什么我立即在同一行为它们分配默认值...

我需要使对象可选地为空,并且仍然能够从中解构未定义的值,因此我可以为它们分配默认值(如上面的示例中所示)或将它们与可选的链接运算符一起使用。如何在不输入任何对象的情况下摆脱这个无用的错误?

【问题讨论】:

  • 为什么?如果requiredProp 不必存在于您的对象上,那么它并不是真正需要的。如果您不知道要在对象上使用什么道具,请使用Record。如果您知道它将来自一组属性,但希望允许省略任何字段,请使用Partial。否则,请始终提供所需的道具,不要将其键入为ISample | {}
  • 如果对象不为空,requiredProp 必须存在。该对象可以为空,因为应用程序可能仍处于获取状态并等待数据,但是当数据进入时,它必须与接口匹配。
  • null{} 更好用。可能在您的状态中包含布尔值 hasLoaded
  • 我仍然想知道是否有任何解释,即使我提供了默认值,Typescript 也不允许我访问可选的联合对象属性。
  • 因为sampleObj 的可能状态之一是{},其中requiredProp 不可用。使用您拥有的联合,如果不声明类型,您将无法成功访问 sampleObj 的任何属性。

标签: typescript


【解决方案1】:

简而言之,您无法访问 sampleObj 上的 ISample 属性,因为 TypeScript 无法推断出联合 ISample | {}{} 成员,因为两个联合成员都对分配感到满意。因此,您无法访问 sampleObj 上的属性,除非它存在于联合体的所有成员中(排除所有属性)。

工会

来自handbook

联合类型是由两个或多个其他类型组成的类型,表示可能是这些类型中的任何一个的值。我们将这些类型中的每一种称为联合的成员

请记住,联合不组合类型(这是intersection 的作用);它们为您提供在分配时可能是什么东西(变量、参数、属性等)的选项。例如:

// foo, by annotation, might be a string or a number...
let foo: string | number = 'bar';
// ... but it is assigned to a string, so its realized type is string:
foo.toUpperCase(); // ok, because foo: string

// We can reassign foo as long as it is still a member of the original annotated union
foo = 0;
foo.toUpperCase(); // error, because foo: number
//  ^^^^^^^^^^^ Property 'toUpperCase' does not exist on type 'number'

这里根据分配推断出更窄的foo 类型(或者更确切地说,工会成员string 被区分)。

再次强调,歧视工会成员(在分配时或其他情况下)会在重新分配之前禁止这些成员,即您不能这样做:

let foo: { bar: string } | { baz: number } = { bar: 'foobar' }
foo.baz = 0
//  ^^^ Property 'baz' does not exist on type '{ bar: string; }'.
foo = { baz: 0 } // ok

空类型{}

Typing a thing as {} can lead to some behaviors that feel unexpected but are totally valid.

...空类型似乎出乎意料:

class Empty {}
 
function fn(arg: Empty) {
  // do something?
}
 
// No error, but this isn't an 'Empty' ?
fn({ k: 10 });

考虑以下有效的 TypeScript:

let foo: {} = {};
foo = { bar: 0 };
foo = 'bar';
foo = [ true, 0, '' ];

这些都是有效的,因为{} 上的每个属性都存在于这些实际类型中的每一个上。如果我们改变它:

  let foo: { length: number } = { length: 0 };
  const o = { bar: 0 }
  foo = o;
//^^^ Property 'length' is missing in type '{ bar: number; }' 
//      but required in type '{ length: number; }'
  foo = 'bar';
  foo = [ true, 0, '' ];

在这里,除了其中一个之外,所有这些都满足length: number{} 没有要满足的属性,所以什么都可以(nullundefined 除外)。

还要注意{} 类型在没有类型断言的情况下实际上是无用的:

let foo: {} = 'bar';
foo.length;
//  ^^^^^^ Property 'length' does not exist on type '{}'

结论/答案

所以,再次查看发布的示例:

interface ISample {
  requiredProp: string
  optionalProp?: string
}

const sampleObj: ISample | {} = { requiredProp: "hello" } // object can now be empty

const { requiredProp = "default", optionalProp = "default" } = sampleObj
//      ^^^^^^^^^^^^              ^^^^^^^^^^^^
// Property 'requiredProp' does not exist on type '{} | ISample'
// Property 'optionalProp' does not exist on type '{} | ISample'.

console.log(requiredProp, optionalProp)

在分配sampleObj 的初始值时,TypeScript 会尝试通过区分与给定值不匹配的成员来缩小类型。 在这种情况下,它不能排除任何工会成员,因为{ requiredProp: "hello" } 同时满足ISample{}


为了彻底起见,请注意const { requiredProp = "default", optionalProp = "default" } = sampleObj 正在尝试访问sampleObj.requiredPropsampleObj.optionalProp,它们不能存在于类型{} | ISample 上。您的选择是:

  • 更改 ISample 以便它可以允许空对象(通过使所有属性可选)。
  • sampleObj 的类型更改为使用Partial<ISample>,这样可以在不更改ISample 的定义的情况下完成相同的操作。
  • 始终将类型为 ISample 的事物的所需属性分配给默认值。

无论您的选择如何,您都应该从联合中删除 {} 以避免头痛(尽管您可能会查看 type predicates)。

【讨论】:

    猜你喜欢
    • 2022-01-22
    • 2020-10-07
    • 1970-01-01
    • 1970-01-01
    • 2019-03-01
    • 1970-01-01
    • 2018-07-07
    • 1970-01-01
    • 2021-07-03
    相关资源
    最近更新 更多