传递任何或记录时,重载的函数签名与TypeScript中的顺序匹配

传递任何或记录时,重载的函数签名与TypeScript中的顺序匹配,typescript,Typescript,我遇到了一个问题,Record(当然也有Record)与我没有预料到的类型匹配 以下代码举例说明了这种情况: 接口ObjWithProp{ 属性?:布尔; [键:字符串]:任意; } 接口ObjWithPropFalsy扩展了ObjWithProp{ 道具?:假; } 接口ObjWithPropTruthy扩展ObjWithProp{ 道具:正确; } 常数a:Record={prop:true}; 常数b:ObjWithPropFalsy=a; 如果(b.prop){//b.prop的类型为f

我遇到了一个问题,
Record
(当然也有
Record
)与我没有预料到的类型匹配

以下代码举例说明了这种情况:

接口ObjWithProp{
属性?:布尔;
[键:字符串]:任意;
}
接口ObjWithPropFalsy扩展了ObjWithProp{
道具?:假;
}
接口ObjWithPropTruthy扩展ObjWithProp{
道具:正确;
}
常数a:Record={prop:true};
常数b:ObjWithPropFalsy=a;
如果(b.prop){//b.prop的类型为false |未定义
log(“键入表示它永远不会发生”)
}否则{
log(“键入表示它总是发生”)
}
运行上面的代码会打印出
类型,表示它从未发生过

如果我不能将
未知
分配到
false |未定义
中,为什么Typescript允许将
记录
分配到类型
ObjWithPropFalsy

我遇到这个问题是因为我提供了多个函数签名:

函数f(arg:ObjWithPropTruthy):数字;
函数f(arg:ObjWithPropFalsy):字符串;
函数f(arg:ObjWithProp):数字|字符串;
函数f(arg:ObjWithProp):数字|字符串{
if(arg.prop)返回0;
否则返回“0”
}
但是使用松散类型的对象调用函数会匹配第一个签名,而不是最后一个签名:

const c:Record={prop:true};//海皮斯和任何人都一样
常数d=f(a);
//^type是'string'而不是'number | string'(或者希望是'number')

如何在上面的重载示例中实现预期的返回类型推断?

您看到了javascript和typescript中发生的混合情况。属性是在javscript中计算的,在typescript中,没有任何东西可以阻止ObjWithPropTruthy和ObjWithPropFalsy同时运行到if语句中。如果查看输出js,您可以看到所有类型信息都消失了

"use strict";
const a = { prop: true };
const b = a;
if (b.prop) {
    console.log("typing says it never happens");
}
else {
    console.log("typing says it always happens");
}
function f(arg) {
    if (arg.prop)
        return 0;
    else
        return "0";
}

const c = { prop: true }; // hapens with any as well
因为您正在显式强制转换
a
,所以使用
prop:true
的赋值不会缩小类型范围,并允许您使用它设置b,因为您已经显式设置了它。基本上,您已经将prop:true定义为typescript遗忘的类型,因为您已经将它定义为一个更松散的类型。在你的情况下,你正在寻找类型的警卫

interface ObjWithProp {
    prop?: boolean;
    [key: string]: any;
}

interface ObjWithPropFalsy extends ObjWithProp {
    prop?: false;
}
interface ObjWithPropTruthy extends ObjWithProp {
    prop?: true;
}

function TruthyCheck(obj:any ):obj is ObjWithPropTruthy{
  return obj && obj.prop === true;
}

function FalsyCheck(obj:any ):obj is ObjWithPropTruthy{
  return obj && obj.prop === false;
}

const a: Record<string, unknown> = { prop: true };
const b: ObjWithPropFalsy = a;



function f(arg: ObjWithPropTruthy): number;
function f(arg: ObjWithPropFalsy): string;
function f(arg: ObjWithProp): number | string | undefined {
    if (TruthyCheck(arg)) return 0;
    else if (FalsyCheck(arg))  return "0"
    return undefined;
}



const c: Record<string, unknown> = { prop: true }; // hapens with any as well
console.log(f(b))
console.log(f(c))
接口ObjWithProp{
属性?:布尔;
[键:字符串]:任意;
}
接口ObjWithPropFalsy扩展了ObjWithProp{
道具?:假;
}
接口ObjWithPropTruthy扩展ObjWithProp{
道具?:正确;
}
函数TruthyCheck(obj:any):obj是ObjWithPropTruthy{
返回obj&&obj.prop===true;
}
函数FalsyCheck(obj:any):obj是ObjWithPropTruthy{
返回obj&&obj.prop===false;
}
常数a:Record={prop:true};
常数b:ObjWithPropFalsy=a;
函数f(arg:ObjWithPropTruthy):数字;
函数f(arg:ObjWithPropFalsy):字符串;
函数f(arg:ObjWithProp):数字|字符串|未定义{
if(TruthyCheck(arg))返回0;
否则如果(FalsyCheck(arg))返回“0”
返回未定义;
}
常数c:Record={prop:true};//海皮斯和任何人都一样
控制台日志(f(b))
控制台日志(f(c))

我删除了一个不必要的重载定义。我还添加了undefined作为返回类型,您不需要这样做

如果我不能将unknown赋值为false | undefined,为什么Typescript允许将记录赋值到类型ObjWithPropFalsy中

看起来Typescript对索引签名的赋值非常轻松(比如
Record
is)因此,如果接口仅由可选项组成,则Typescript认为任何索引签名都是匹配项。这是相当危险的,但事实就是如此(从TS 4.0.2开始)。这反映了每个对象在JavaScript中都是映射类型的奇怪之处

如何在上面的重载示例中实现预期的返回类型推断

第一个重载是
记录
将匹配的内容,但不是更具体的类型。使用
never
是一种方法:

函数f(arg:{prop?:never}):数字|字符串;
函数f(arg:ObjWithPropTruthy):数字;
函数f(arg:ObjWithPropFalsy):字符串;
函数f(arg:ObjWithProp):数字|字符串;
函数f(arg:ObjWithProp):数字|字符串{
if(arg.prop)返回0;
否则返回“0”
}

这样,当将
any
Record
作为参数传递时,第一个重载将匹配。当传递较窄的类型(如
ObjWithPropTruthy
)时,第一个重载将不匹配,因为
never

我理解为什么执行会进入第一个if。我不明白的是,如果我不能将
未知
分配给
false |未定义
,为什么Typescript允许我将
录制为
ObjWithPropFalsy
(无显式强制转换)。对不起,我要澄清这个问题。在这个例子中,如果
ObjWithPropTruthy
具有
prop:true
而不是
prop?:true
,那么
f(c)
返回
string
而不是
number
。unknown只能分配给自身和any,然而,根据typescript,任何东西都可以分配给unknown。谢谢,我知道。这正是我希望分配
b=a
将被禁止的原因。我相信这是因为
prop
是可选的,所以
a
不需要它。从typescript的角度来看,它没有它,因为您将
a
定义为
Record
,它对
ObjWithPropFalsy
有效,因为它的索引器有
任何
值类型,可以将
未知
分配给它们。