传递任何或记录时,重载的函数签名与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
有效,因为它的索引器有任何值类型,可以将未知
分配给它们。