访问可能不存在于类型上的属性并获取 undefined。

huangapple go评论89阅读模式
英文:

Access property that might not exist on a type and get undefined

问题

以下是您的代码翻译部分:

我有以下的代码:

    type A = {
        x: number;
    } | {
        y: number;
    }
    
    const a = { x: 0 } as A;
    const b = a.x;
并且得到以下错误:

    Property 'x' does not exist on type 'A'.
      Property 'x' does not exist on type '{ y: number; }'.ts(2339)

`b` 的类型是 `any`

我理解,在 `a` 上的 `x` 可能不存在,但在普通的JavaScript中,访问一个不存在的属性时,您总是会得到 `undefined`。因此,我希望TypeScript返回给我 `number | undefined`

-----------

一个更简单的例子:

    const bar = ({}).foo;

`bar` 应该是 `undefined`,但是却是 `any`

如何告诉TypeScript在访问可能不存在的属性时给我 `undefined` 而不是 `any`,并在出现错误时报错?

<details>
<summary>英文:</summary>

I have the following code:

    type A = {
        x: number;
    } | {
        y: number;
    }
    
    const a = { x: 0 } as A;
    const b = a.x;
And get the following error:

    Property &#39;x&#39; does not exist on type &#39;A&#39;.
      Property &#39;x&#39; does not exist on type &#39;{ y: number; }&#39;.ts(2339)

`b` is of type `any`

I understand, that `x` on `a` might not exist, but in normal Javascript, when accessing a property that does not exist, you always get `undefined`. Therefore, I would expect TS to return me `number | undefined`.

-----------

An even more simple example: 

    const bar = ({}).foo;

`bar` should be undefined, but is any.

--------

How can I tell Typescript to give me `undefined` instead of `any` and an error when accessing a property which might not exist?

</details>


# 答案1
**得分**: 2

Object types in TypeScript can have more properties than mentioned in their declarations

TypeScript中,对象类型可以拥有比声明中指定的属性更多的属性。

Object types in TypeScript are open or extensible and not closed or sealed. A value of a type can have more properties than are specified in that type. Just because a type doesn't mention a property, it doesn't mean that a value of that type is necessarily missing that property. In other words, object types in TypeScript cannot be made "exact", as requested in microsoft/TypeScript#12936.

TypeScript中的对象类型是开放的或可扩展的,而不是封闭的。类型的值可以拥有比该类型中指定的属性更多的属性。仅因为类型没有提到一个属性,并不意味着该类型的值一定缺少该属性。换句话说,在TypeScript中,对象类型无法被“精确定义”,就像在[microsoft/TypeScript#12936](https://github.com/microsoft/TypeScript/issues/12936)中请求的那样。

For example:

例如

interface Y {
  y: number;
}

We know that a value of type `Y` must have a `y` property of type `number`:

我们知道类型为`Y`的值必须拥有一个类型为`number``y`属性:

let y: Y;
y = {}; // error, Property 'y' is missing in type '{}' but required in type 'Y'.
y = { y: "oops" }; // error, Type 'string' is not assignable to type 'number'.
y = { y: 123 }; // okay

And it might even seem that other properties are prohibited. After all, if you assign an object literal with extra properties to `y`, you get an error:

甚至可能会看到其他属性是被禁止的。毕竟,如果你将带有额外属性的对象文字分配给`y`,你会得到一个错误:

y = { x: "oops", y: 123 }; // error!
// Object literal may only specify known properties, and 'x' does not exist in type 'Y';

But this error is not happening because `Y` doesn't allow an `x` property; it's because the object literal is being assigned directly to something which will forget about the `x` property. That is, this excess property checking is more of a linter rule than a type system check, and it specifically targets object literals and not all values:

但是,这个错误并不是因为`Y`不允许有一个`x`属性;而是因为对象文字直接分配给了一个会忘记`x`属性的东西。也就是说,这个多余属性检查更像是一个代码检查规则,而不是一个类型系统检查,它专门针对对象文字,而不是所有的值:

const xy = { x: "oops", y: 123 };
y = xy; // okay, no error

The above is accepted because xy will remember the x property, so it's accessible somewhere. When you assign y = xy, xy is just a value and not an object literal, so there's no error.

以上之所以被接受是因为xy会记住x属性,所以它在某个地方是可访问的。当你分配y = xy时xy只是一个值,而不是一个对象文字,所以没有错误。

Indeed, allowing excess properties is required for extending interfaces to work as expected:

事实上,允许多余的属性是为了使扩展接口按预期工作:

interface XY extends Y {
  x: string;
}

const val: XY = xy; // okay

If you couldn't add properties to types without invalidating those types, a lot of interface and class hierarchies would no longer be type hierarchies, and that would break both structural typing and JavaScript native classes like DOM elements.

如果不能向类型添加属性而不使这些类型无效,那么许多接口和类层次结构将不再是类型层次结构,这将破坏结构化类型和JavaScript本机类(如DOM元素)。

Therefore a property not mentioned in every member of a union could hold any value whatsoever if it exists

因此,如果联合的每个成员中没有提到的属性存在,它可能包含任何值

In your example you have a union type equivalent to

在你的示例中,你有一个等同于的联合类型

type A = { x: number; } | Y;

As we showed above, a value can be of type `Y` while also having properties `Y` doesn't know about. Specifically, the `XY` type has an `x` property of type `string`. So the following assignment is accepted:

正如我们在上面所展示的,一个值可以是类型`Y`,同时还具有`Y`不知道的属性。具体来说,`XY`类型有一个类型为`string``x`属性。因此,下面的赋值被接受:

const a: A = Math.random() < 0.5 ? { x: 123 } : val; // okay

And now we immediately hit a problem with checking the x property of a. If the compiler allowed us to assume that a.x were either number or undefined, we could write

现在,我们立刻遇到了检查`a``x`属性的问题。如果编译器允许我们假设`a.x`要么是`number`,要么是`undefined`,我们可以编写

a.x?.toFixed()

with no warning. And you'd get a runtime error because a.x is a string and not a number, and has no toFixed() method. So you get a compiler error

没有警告。并且你将会得到一个运行时错误,因为`a.x`是一个`string`,而不是一个`number`,并且没有`toFixed()`方法。所以你会得到一个编译器错误

try {
  a.x?.toFixed(); // error!
  //~ &lt;-- Property 'x' does not exist on type 'A'.
  //a.x is of type any
} catch (e) {
  console.log(e) // a.x.toFixed is not a function
}

saying that x does not exist on A, and the type of a.x is inferred to be the any type. This is the best the compiler can do (well, it should probably be the more type-safe unknown type instead of any, but unknown is a more recent addition to the type system than any and it would be a big breaking change to do that now).



<details>
<summary>英文:</summary>

**Object types in TypeScript can have more properties than mentioned in their declarations**

Object types in TypeScript are *open* or *extensible* and not *closed* or *sealed*. A value of a type can have more properties than are specified in that type.  Just because a type doesn&#39;t mention a property, it doesn&#39;t mean that a value of that type is necessarily missing that property.  In other words, object types in TypeScript cannot be made &quot;*exact*&quot;, as requested in [microsoft/TypeScript#12936](https://github.com/microsoft/TypeScript/issues/12936).

For example:

    interface Y {
      y: number;
    }

We know that a value of type `Y` must have a `y` property of type `number`:

    let y: Y;
    y = {}; // error, Property &#39;y&#39; is missing in type &#39;{}&#39; but required in type &#39;Y&#39;.
    y = { y: &quot;oops&quot; }; // error, Type &#39;string&#39; is not assignable to type &#39;number&#39;.
    y = { y: 123 }; // okay

And it might even seem that other properties are prohibited.  After all, if you assign an [object literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer) with extra properties to `y`, you get an error:

    y = { x: &quot;oops&quot;, y: 123 }; // error!
    // Object literal may only specify known properties, and &#39;x&#39; does not exist in type &#39;Y&#39;

But this error is not happening because `Y` doesn&#39;t *allow* an `x` property; it&#39;s because the object literal is being assigned directly to something which will *forget* about the `x` property.  That is, this [excess property checking](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-1-6.html#stricter-object-literal-assignment-checks) is more of a linter rule than a type system check, and it specifically targets object literals and not all values:

    const xy = { x: &quot;oops&quot;, y: 123 };
    y = xy; // okay, no error

The above is accepted because `xy` will remember the `x` property, so it&#39;s accessible *somewhere*.  When you assign `y = xy`, `xy` is just a value and not an object literal, so there&#39;s no error.

Indeed, allowing excess properties is required for [extending interfaces](https://www.typescriptlang.org/docs/handbook/2/objects.html#extending-types) to work as expected:

    interface XY extends Y {
      x: string;
    }
    
    const val: XY = xy; // okay

If you couldn&#39;t add properties to types without invalidating those types, a lot of interface and class hierarchies would no longer be type hierarchies, and that would break both [structural typing](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html#structural-type-system) and JavaScript native classes like DOM elements.

---

**Therefore a property not mentioned in every member of a union could hold any value whatsoever if it exists**

In your example you have a [union type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types) equivalent to

    type A = { x: number; } | Y;

As we showed above, a value can be of type `Y` while also having properties `Y` doesn&#39;t know about.  Specifically, the `XY` type has an `x` property of type `string`.  So the following assignment is accepted:
    
    const a: A = Math.random() &lt; 0.5 ? { x: 123 } : val; // okay

And now we immediately hit a problem with checking the `x` property of `a`.  If the compiler allowed us to assume that `a.x` were either `number` or `undefined`, we could write

    a.x?.toFixed() 

with no warning.  And you&#39;d get a runtime error because `a.x` is a `string` and not a `number`, and has no `toFixed()` method.  So you get a compiler error

    try {
      a.x?.toFixed(); // error!
      //~ &lt;-- Property &#39;x&#39; does not exist on type &#39;A&#39;.
      //a.x is of type any
    } catch (e) {
      console.log(e) // a.x.toFixed is not a function
    }

saying that `x` does not exist on `A`, and the type of `a.x` is inferred to be [the `any` type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#any).  This is the best the compiler can do (well, it should probably be the more type-safe [`unknown` type](https://www.typescriptlang.org/docs/handbook/2/functions.html#unknown) instead of `any`, but `unknown` is a more recent addition to the type system than `any` and it would be a big breaking change to do that now).

If you want to safely access a property of a union, it should be mentioned in every member of that union.  If you intend it to be missing or `undefined`, you should say so by making it an [optional](https://www.typescriptlang.org/docs/handbook/2/objects.html#optional-properties) property of type `undefined` or [the impossible `never` type](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-never-type):

    type A = { x: number, y?: never } | { x?: never, y: number };

Then the compiler will prevent you from assigning an `XY` to an `A`:

    let a: A;
    a = val; // error!

And then `a.x` will definitely be `number` or `undefined`:
   
    a = Math.random() &lt; 0.5 ? { x: 123 } : { y: 123 }; // okay
    a.x?.toFixed(); // okay

----

**The TypeScript type system isn&#39;t fully sound so you could use the `in` operator if you want**

So there you go; you can&#39;t access properties of a union unless they are mentioned in every member of that union, because object types aren&#39;t exact, and the compiler will complain when you try to violate type safety.  Because type safety is very important.  

But that can&#39;t be the whole story. [TypeScript Language Design Non-Goal](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals#non-goals) #3 is

&gt; Apply a sound or &quot;provably correct&quot; type system. Instead, strike a balance between correctness and productivity.

There are [intentionally places in TypeScript where type safety is ignored in favor of usability](https://www.typescriptlang.org/docs/handbook/type-compatibility.html#a-note-on-soundness). These places were chosen by human beings, for human reasons.  There might be some objective data showing that certain soundness holes lead to fewer problems than you&#39;d get if you fixed them, but ultimately this is at the discretion of the TS language design team.

So, one such unsoundness (as you mentioned in the comments) is the [use of the `in` operator to narrow unions](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-in-operator-narrowing):

    if (&quot;x&quot; in a) {
      a.x.toFixed(2); // no compiler error
    } else {
      a.y.toFixed(2); // no compiler error
    }

By checking that `a` has an `x` key, the compiler narrows `a` from `A` to `{x: number}`, and lets you access `a.x.toFixed()` with no warning.  But this has the same problem as shown above, and indeed, you can get the compiler to agree to this and then hit a runtime error whan `a.x` turns out to be a `string`.

And the only answer I can give for why this happens is that the TypeScript team has decided that it&#39;s better to allow this than to forbid it.  As mentioned in a [comment](https://github.com/microsoft/TypeScript/issues/39065#issuecomment-643826453) on a [microsoft/TypeScript#39065](https://github.com/microsoft/TypeScript/issues/39065), a declined request to allow you to assume, as you originally thought, that properties of unions are either present or `undefined`:

&gt; The &quot;lack&quot; of this feature is very much intentional; this pattern is not safe to use because object types aren&#39;t closed, so the existence of a value in some property slot is really not a solid indication that anything correct is happening. The `in` operator is granted an exception to this because there&#39;s nothing else you might be intending to do with the `in` operator. 

So another approach you could take is to use the `in` operator as shown above before accessing the property, with the assumption that edge cases like `XY` will not show up in practice.  

[Playground link to code](https://www.typescriptlang.org/play?target=99#code/FASwdgLgpgTgZgQwMZQAQE1UG9itQTwC5UwBXAWwCNYBuYAX2GABsoIDj079UBebejVQB6YalgwA9jAA0qAApSADrAg8A5PnWoQAZ1Tk9u8AHMdYVGpWp1Wetsql2MKAEdSIFwBNzl-NfV0dQA6YB5+LA5UACJJSSVdaNRBETEJaTkAFX80dV0IGFNtPRJJdgRdYxMwBEpWS0k-ALIqWBCwvmwogEYAJgBmZKFRVEkAawR8JnCugA9iWPjEuSJUPsGUkfSYAEJgEbwAeUoAKygkdmYQaBgEZgNJ0bBmHl0VJBA4HjGwSQB3CxKZSqEBQXRyBBgHzqWbaLySMGldhQWZ6djgJq5IJMJCSMD5VCzGaReYxOIJaIrYjrIYdfhE4ZicaTOS-cQwKQwJjgG6IFCoAAamBR0Ch+kwODwpPyhTAJjojGAuPx7AAbndiELOgzUqMJlNgFY0ABBTok4gtagwIT0VAAHwwdCVeIJCGIpv4AFkEBAABbBW5QyTkAAUAEpUAAeVAABmCAFZUAB+ObUgbJVDEdXMRlRgC0ecsvpKulISBQUC8+moSAQpF0aC1IqgYowqEhPmFs1FVdQxpxLvYlE6CGCs1zkYLRZKJUKJl9EBe7bAPBD5Em1B07F0vskpGYPk3pDAP3+YAjHaRJAoVvtqGPXigcHAlaYBR4kvbY6TwQgkgAYiAsyVuGubbHseCiAAfvmhaKPEqgaLCqDwoivzIqiBJ4piNhBMEIYDP0ACcYa4Kko6zDo+iSHAOGQlMtq1hASC+qgIZQBGn7KrokisMEzCSCY7ERiMFG-gBQGVlRV4IKgcDHhcIB4gwTDADU5BgkoyBoIBlE4GRRp9mahIWjesArEmFpQKqsAZg6JKWSQ1nmVElq2YITB4Kw5TunQeCyfw2ZgRy0gQe2nTen6AYdsG4ZRrGCbJqmazpraxCRKsNKbEy+qeV+sw-n+ukgWGubMgaKmGjAH55Z8bHRLMSQYggnFkf5Y7icVXgEaVupsri5BKCArAwOynJkbaUDMA22BtV++CdZJ3W9L1Iz9cGQ0jWN0gTUwjE+ixbEcbNeDcbxUD8YJwkMEAA)






</details>



huangapple
  • 本文由 发表于 2023年2月6日 06:53:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/75356058.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定