我正在try 从外部依赖项扩充接口. 原因是外部d.ts文件没有指定完整的返回类型. 在下面的示例中,doStuff()实际上存在于由Bar.giveList()Baz.giveList()返回的对象上,但原始声明不包含此信息. 以下是使用declare global的问题的重新构建(我实际上是在try 增加命名模块,但当在单个文件中使用两个declare global块时,该问题似乎类似).

export type MyList<T> = T[] & {
    doStuff(): void
}

declare global { // Original declaration from external dependency
    export interface Bar {
        giveList(): string[]
    }
    export interface Baz {
        giveList(): string[]
    }
}

declare global { // My attempt of refining some return types
    export interface Bar {
        giveList(): MyList<string>
    }
    export interface Baz {
        giveList(): MyList<string>
    }
}

let bar        = {} as Bar
let baz        = {} as Baz
let barbaz     = {} as (Bar | Baz)

let barList    = bar.giveList()     // infered type: MyList<string> --- good
let bazList    = baz.giveList()     // infered type: MyList<string> --- good
let barbazList = barbaz.giveList()  // infered type: string[]       --- why not MyList<string>?

barList.doStuff()                   // good
barList[0]                          // good
bazList.doStuff()                   // good
bazList[0]                          // good
barbazList.doStuff()                // ERROR: Property 'doStuff' does not exist on type 'string[]'.(2339)
barbazList[0]                       // good

既然Bar.giveList()Baz.giveList()返回MyList<string>,为什么(Bar|Baz).giveList()不也返回MyList<string>呢?

推荐答案

这是已知的字体设计限制;请参阅microsoft/TypeScript#50488以获得权威答案.


documentation for declaration merging:

对于函数成员,具有相同名称的每个函数成员被视为描述同一函数的重载.同样值得注意的是,在接口A与之后的接口A合并的情况下,第二接口将具有比第一接口更高的优先级.

所以BarBaz最终相当于:

interface Bar {
  giveList(): MyList<string>
  giveList(): string[]
}
interface Baz {
  giveList(): MyList<string>
  giveList(): string[]
}

其中giveList()overloaded的方法.


不幸的是,重载在Typescript 中是脆弱的.

对声明的重载方法的直接调用将解析为最合适的调用签名.这就是在bar.giveList()baz.giveList()中发生的事情;在这两种情况下,编译器都会遍历调用签名列表并 Select 最合适的一个.它是较高的优先级giveList(),返回MyList<string>.

但对重载方法的任何其他操作或分析都会导致TypeScrip走捷径.它基本上只 Select 了一个调用签名,而没有计算出哪个是最合适的.通常,这是具有least优先级的那个,假设它是最一般的(这个假设在您的示例中是不正确的,这就是事情出错的原因).因此,当确定union Bar | Baz中的giveList()的类型时,编译器只 Select 优先级最低的签名,即返回string[]的签名.然后你有一个返回string[]的事物的联合,你得到string[].


这就是正在发生的事情.实际上,您并不想在giveList()方法中返回merge,但实际上modify将现有的BarBaz接口返回MyList<string>,而不是string[].但是,除了手动派生库和在本地修改类型之外,TypeScrip没有修改现有接口的功能.

microsoft/TypeScript#19064有一个功能请求,要求允许用户重载remove个,然后大概用其他重载覆盖它们.这真的是你想要做的.但它没有得到TypeScrip的支持,而且由于缺乏压倒性的社区支持(它只有几个赞成票),我不希望看到它在短期内实现(即使是非常受欢迎的功能通常也不会很快实现).

Typescript相关问答推荐

为什么在将条件返回类型的字符串赋给数字时没有类型错误?

有没有办法适当缩小类型?

对于使用另一个对象键和值类型的对象使用TS Generics

如何在编译时为不带常量的参数引发错误

在Reaction-Native-ReAnimated中不存在Animated.Value

迭代通过具有泛型值的映射类型记录

APP_INITIALIZER在继续到其他Provider 之前未解析promise

基元类型按引用传递的解决方法

try 呈现应用程序获取-错误:动作必须是普通对象.使用自定义中间件进行MySQL操作

有没有可能创建一种类型,强制在TypeScrip中返回`tyPeof`语句?

类型推断对React.ForwardRef()的引用参数无效

ANGLE NumberValueAccessor表单控件值更改订阅两次

对于VUE3,错误XX不存在于从不存在的类型上意味着什么?

Typescript不允许在数组中使用联合类型

导航链接不触发自定义挂钩

如何键入';v型';

两个子组件共享控制权

如何在由函数参数推断的记录类型中具有多个对象字段类型

是否可以使用元组中对象的键来映射类型

为什么我的 Typescript 函数中缺少 void 隐式返回类型?