简体   繁体   English

为什么 TypeScript 不能推断相同对象类型的赋值?

[英]Why TypeScript can't infer assignments of the same object type?

Let's say I have this very simplified example of what's happening in my code:假设我有一个非常简单的例子来说明我的代码中发生的事情:

interface I {
  n?: number;
  s?: string;
}

const a: I = {
  n: 1,
}

const b: I = {
  n: 2,
  s: 'b',
}

const props = ['n', 's'] as const;

for (const prop of props) {
  if (!a[prop]) {
    // here, I get the following error:
    // TS2322: Type 'string | number' is not assignable to type 'never'.
    //   Type 'string' is not assignable to type 'never'.
    a[prop] = b[prop];
  }
}

Being a and b the same types, accessing the same property prop ... shouldn't it be possible?作为ab相同的类型,访问相同的属性prop ......难道不应该吗?

  • If b[prop] is a number, then a[prop] should accept numbers如果b[prop]是一个数字,那么a[prop]应该接受数字
  • If b[prop] is a string, then a[prop] should accept strings如果b[prop]是一个字符串,那么a[prop]应该接受字符串
  • If b[prop] is undefined, is because that prop is optional ?如果b[prop]未定义,是因为该 prop 是可选的?

So, what am I missing here?那么,我在这里错过了什么?

Update: Since I simplified my example too much, the answer was to remove the as const part... But I think it's due to my environment, because I'm using strict: true in tsconfig.json ...更新:由于我过多地简化了我的例子,答案是删除as const部分......但我认为这是由于我的环境,因为我在tsconfig.json使用了strict: true ...

then, if I try accessing without as const , I get this the TS7053 error:然后,如果我尝试不使用as const访问,则会收到 TS7053 错误:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'I'.元素隐式具有 'any' 类型,因为类型 'string' 的表达式不能用于索引类型 'I'。

No index signature with a parameter of type 'string' was found on type 'I'.在类型 'I' 上找不到带有类型 'string' 参数的索引签名。

Fix adding the as const or doing for(const prop of props as ('n' | 's')[]) {修复添加as const或执行for(const prop of props as ('n' | 's')[]) {

and then is when I get my original error (which can be easily fixed with as any but I'm trying to avoid that)然后是当我得到我的原始错误时(可以很容易地修复as any错误,但我试图避免这种情况)

In general, Typescript doesn't prove every provable property of your code, and isn't trying to.通常,Typescript 不会证明代码的所有可证明属性,也不会尝试这样做。 In this particular case, we humans know that the type of b[prop] matches the type of a[prop] because the property names are the same, but Typescript doesn't see that because it doesn't have a rule which would allow it to.在这种特殊情况下,我们人类知道的类型b[prop]类型匹配a[prop]因为属性名相同,但打字稿没有看到,因为它没有一个规则,将允许它到。

If we follow Typescript's logic:如果我们遵循 Typescript 的逻辑:

  • props is declared as a tuple of type ['n', 's'] . props被声明为['n', 's']类型的元组。
  • prop is declared as an element of props , so its type is inferred as 'n' | 's' prop被声明为props一个元素,所以它的类型被推断为'n' | 's' 'n' | 's' . 'n' | 's'
  • Since b is declared as type I , then b[prop] is then inferred as type I['n' | 's']由于b被声明为类型I ,那么b[prop]然后被推断为类型I['n' | 's'] I['n' | 's'] which simplifies to string | number I['n' | 's']简化为string | number string | number . string | number
  • Since a is declared as type I , then the assignment target a[prop] can only receive a value which is assignable to any property that prop might name;由于a被声明为类型I ,那么赋值目标a[prop]只能接收一个可赋值给prop可能命名的任何属性的值; so the assignment target's type is inferred as I['n'] & I['s'] which simplifies to string & number , and then never .所以分配目标的类型被推断为I['n'] & I['s'] ,它简化为string & number ,然后never
  • So as far as Typescript is concerned, the assignment a[prop] = b[prop] is assigning a value of type string | number因此,就 Typescript 而言,赋值a[prop] = b[prop]是赋值string | number类型的值。 string | number to an assignment target of type never . string | number到类型为never的赋值目标。 Since string | number由于string | number string | number is not assignable to never , this is a type error. string | number不能分配给never ,这是一个类型错误。

The simplest fix is to use a type assertion like b[prop] as any , ie you tell Typescript that you know your code is type-safe so it doesn't need to be checked.最简单的解决方法是使用像b[prop] as any这样的类型断言b[prop] as any ,即您告诉 Typescript 您知道您的代码是类型安全的,因此不需要检查它。 Another option is to use a helper function which does the assignments in a way that satisfies Typescript's type-checker:另一种选择是使用一个辅助函数,它以一种满足 Typescript 的类型检查器的方式进行赋值:

function copyMissingProperties<T>(a: T, b: T, props: readonly (keyof T)[]): void {
    for (const prop of props) {
        if (!a[prop]) {
            a[prop] = b[prop]; // ok
        }
    }
}

Here, prop 's type is keyof T which is not a union type, so the assignment target doesn't have an intersection type.这里, prop的类型是keyof T ,它不是联合类型,因此分配目标没有交集类型。

Here is one way I got it to work without making use of casting:这是我在不使用铸造的情况下使其工作的一种方法:

interface I {
    n?: number;
    s?: string;
}

const a: I = {
    n: 1,
}

const b: I = {
    n: 2,
    s: 'b',
}

const props: Array<keyof I> = ['n', 's'];

for (const prop of props) {
    if (!a[prop]) {
        const newProp: I = {[prop]: b[prop]};
        Object.assign(a, newProp);
    }
}

The trick was to use Object.assign.诀窍是使用 Object.assign。 However, to still ensure the type safety, you can declare the new property as an object of type I and then use it in the Object.assign.但是,为了仍然保证类型安全,您可以将新属性声明为类型I的对象,然后在 Object.assign 中使用它。 To make this work in all cases, the best way to do is to just do Object.assign(a, {[prop]: b[prop]})为了在所有情况下都能正常工作,最好的方法是执行Object.assign(a, {[prop]: b[prop]})

the problem is with this line问题出在这条线上

const props = ['n', 's'] as const;

change it to将其更改为

const props = ['n', 's'];

check this stackblitz检查这个堆栈闪电战

I tried like that and its work, and you should remove as const value on props definition我试过这样和它的工作,你应该删除 props 定义上的 const 值

interface I {
  n?: number;
  s?: string;
}

const a: I = {
  n: 1,
}

const b: I = {
  n: 2,
  s: 'b',
}

const props = ['n', 's'];

for (const prop of props) {
  if (!a[prop]) {
    a[prop] = b[prop];
    console.log(a, b);
  }
}

Result is: {n: 1, s: "b"} {n: 2, s: "b"}结果是:{n: 1, s: "b"} {n: 2, s: "b"}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM