首页 > 解决方案 > 为什么允许这样做?常量 nums: number[] = { ...[1, 2, 3] }

问题描述

刚刚在我的打字稿代码库中发现了一个严重错误,因为这是允许的:

const nums: number[] = { ...[1, 2, 3] } // should have been [ ...[1,2,3] ]
a.join('-')
// runtime error: a.join is not a function 

操场

为什么一个数组被解构为一个对象,可以分配给数组,它可以导致一个容易预防的运行时异常?

标签: typescript

解决方案


这是 TypeScript 的设计限制;请参阅microsoft/TypeScript#34780

类型系统没有办法将interface成员标记为“自己的”或可枚举的,因此编译器假定所有成员都是通过扩展运算符复制的。作为一种启发式方法,这通常是足够的,但它对原型上设置的任何成员都做了错误的事情,比如类的方法:

interface Whoops {
    foo(): void;
    a: number;
    b: string;
}

class Oops implements Whoops {
    foo() { }
    a = 1;
    b = "";
}

const oopsie = (w: Whoops) => ({ ...w });

oopsie(new Oops()).foo(); // no compiler error
// runtime error: oopsie(...).foo is not a function!

如果您class直接编写声明,编译器将假定方法声明不可扩展:

declare class Whoops {
    foo(): void;
    a: number;
    b: string;
}
const oopsie = (w: Whoops) => ({ ...w });
oopsie(new Whoops()).foo(); // compiler time error as expected
// foo does not exist on {a: number; b: string};

但不幸的是,类型声明Array<T>是针对 aninterface而不是声明为class。因此,当您将数组传播到对象中时,编译器认为所有Array属性和方法都被复制,因此结果对象符合Array接口,因此具有join()方法。哎呀。


Maaaaybe 有人可以更改标准库,而不是interface Array<T>and interface ArrayConstructoranddeclare var Array: ArrayConstructor我们刚刚拥有declare class Array<T>, 然后join()将不再被视为可传播的,但我不确定。Array当我在自己的系统上本地尝试它时,它似乎可以工作,但我无法在 Playground 或其他在线 IDE 中轻松重现它,并且无论如何我都不愿意使用内置类型。

或者 maaaaybe 可以更改语言,以便可以在 s 上标记非自己或不可枚举的属性interface,但我不会指望它(参见microsoft/TypeScript#9726

目前这是 TypeScript 的设计限制。如果你对此有强烈的感觉,你可以去 microsoft/TypeScript#34780 并给它一个并描述你是如何被它咬的,但我不知道它真的有多大好处。

好的,希望有帮助;祝你好运!

Playground 代码链接


推荐阅读