let变量的重新赋值使TypeScript无法识别动态添加的类型

bvn4nwqk  于 2023-05-19  发布在  TypeScript
关注(0)|答案(1)|浏览(139)

我有一个变量userBalances,它是一个从一开始就拥有多个属性的对象。但是,稍后将分配一个属性,称为categories
发生这种情况的函数需要返回userBalances,并将categories属性返回给它。

// ... stuff before here
userBalances = userBalances.map((token) => {
  return {
    ...props,
    categories: ['a', 'b'], 
  };
});

return userBalances  // <- TS linter saying that `categories` is missing

但是上面的代码不起作用,因为它说categories丢失了。当我将代码修改为以下内容时:

// ... stuff before here
return userBalances.map((token) => {
  return {
    ...props,
    categories: ['a', 'b'], 
  };
}); // fine!

它工作正常。我不明白为什么会这样
编辑:用户余额在该函数的前面声明如下:

// an API result with an array of objects having all properties apart from `categories`
let userBalances = results.filter(({ balance }) => balance.gt(0));
olmpazwi

olmpazwi1#

我猜你的意思是你有这样的东西:

let userBalances = [
    {a: 1},
    {a: 2},
    {a: 3},
];

userBalances = userBalances.map((balance) => {
    return {
        ...balance,
        b: balance.a * 2,
    };
});

console.log(userBalances[0].b); // <== Error: Property 'b' does not exist on type '{ a: number; }'.(2339)

Playground链接
问题是,尽管您已经向userBalances中的元素添加了一个新属性,但userBalancestype 并没有改变,它仍然是{a: number}[](在上面)。在运行时,对象确实有TypeScript不知道的额外属性。
当你执行return版本时,它不会发生,因为(我猜)你已经允许TypeScript从return语句中推断返回类型,而不是在函数定义上提供特定的类型。
通常的做法是赋值给一个新的变量,允许TypeScript推断其类型(就像return一样):

const userBalances = [
    {a: 1},
    {a: 2},
    {a: 3},
];

const updatedBalances = userBalances.map((balance) => {
    return {
        ...balance,
        b: balance.a * 2,
    };
});

console.log(updatedBalances[0].b); // <== Works

Playground链接
在一条评论中,你问:
谢谢你的回答,我的问题。然而,我仍然不明白为什么TS不能看到我重新分配了新的属性。我想你是想通过这句话向我解释:
在运行时,对象确实有TypeScript不知道的额外属性
......但你能再详细一点吗?
我不认为TypeScript * 改变 * 现有变量的类型,当你给它一个新的值。(我不能肯定地说,但我不认为我遇到过它这样做。)它将narrow联合类型变量的上下文类型,但您在上面寻找的是TypeScript更改userBalances的类型,以在数组元素上包含b属性,作为赋值的结果。据我所知,TypeScript并没有这样做。
如果你愿意,你可以直接告诉编译器类型已经改变了,通过某种类型的保护(可能是类型 predicate 函数)或Assert函数。下面是一个使用Assert函数的例子:

function assertElementsHaveNumberProperty<OriginalElementType, NewPropertyName extends PropertyKey>(
    array: OriginalElementType[],
    newProperty: NewPropertyName,
): asserts array is (OriginalElementType & {[K in NewPropertyName]: number})[] {
    for (const element of array) {
        if (typeof (element as any)[newProperty] !== "number") {
            throw new Error(`assertHasBNow failed`);
        }
    }
}

let userBalances = [
    {a: 1},
    {a: 2},
    {a: 3},
];
// Here, the element type of `userBalances is `{a: number}`.

userBalances = userBalances.map((balance) => {
    return {
        ...balance,
        b: balance.a * 2,
    };
});

// Here, the element type of `userBalances is still `{a: number}`.

assertElementsHaveNumberProperty(userBalances, "b");

// Now the element type of `userBalances is `{a: number} & {b: number}`,
// which is the same as `{a: number, b: number}`.

console.log(userBalances[0].b); // <== Works

Playground链接
我不建议这样做,因为这很不寻常(至少在我的经验中是这样),但是这种显式的语句告诉编译器userBalances的类型已经改变是可能的。我建议使用一个新的具有推断类型的变量。它也不那么麻烦(不需要Assert函数)。

相关问题