TypeScript:缩小对象字段的类型

TypeScript:缩小对象字段的类型,typescript,Typescript,我得到了一个嵌套很深的对象和函数,它一层一层地“展开”。但当最后一个“层”得到可选的TypeScript时,不知何故它就失去了对其类型的跟踪 我希望result的类型是string而不是string{c:string},代码如下: 是bug还是预期行为 const obj={ a:{ b:{ c:“str” }, b1:‘b1’ }, a1:‘a1’ } 类型DeepObj=类型obj 窄带型obj=O[K]; 常量getDeepVal=< O.obj, K1扩展了DeepObj的关键功能,

我得到了一个嵌套很深的对象和函数,它一层一层地“展开”。但当最后一个“层”得到可选的TypeScript时,不知何故它就失去了对其类型的跟踪

我希望
result
的类型是
string
而不是
string{c:string}
,代码如下:

是bug还是预期行为

const obj={
a:{
b:{
c:“str”
},
b1:‘b1’
},
a1:‘a1’
}
类型DeepObj=类型obj
窄带型obj=O[K];
常量getDeepVal=<
O.obj,
K1扩展了DeepObj的关键功能,
K2扩展了窄带OBJ的键,
K3扩展了窄带OBJ的键
> (
o:o,
k1:k1,
k2:k2,
k3?:k3
) => {
返回k3?o[k1][k2][k3]:o[k1][k2]
}
//结果:string |{c:string;}而不是string
const result=getDeepVal(对象“a”、“b”、“c”)

它是预期行为:
getDeepVal
的返回类型是
O[K1][K2]| O[K1][K2][K3]
。编译器不会合成检查是否定义了
K3
的条件类型,并相应地缩小返回类型的范围。实际上,即使
getDeepVal(obj,'a','b')
也会为
K3
推断
'c'
,因此即使
K3
未定义的
K3
也不会。这里的解决方案是更改签名以检测值是否未定义,并显式地使用我们自己的条件类型来表示它。有一种可能性:

const getDeepVal = <
    O extends DeepObj,
    K1 extends keyof O,
    K2 extends keyof O[K1],
    K3R extends [(keyof O[K1][K2])?]
>(
    o: O,
    k1: K1,
    k2: K2,
    ...[k3]: K3R
) => {
    return (k3 ? o[k1][k2][k3!] : o[k1][k2]) as
        K3R[0] extends infer K3 ? (
            K3 extends keyof O[K1][K2] ? O[K1][K2][K3] : O[K1][K2]
        ) : never;
}
看起来不错


请注意,这是一个具有条件类型的相当复杂的签名。处理此问题的另一种方法是分别表示
k3
-存在和
k3
-不存在的行为。像这样:

// Call signatures
function getDeepVal<O extends DeepObj, K1 extends keyof O, K2 extends keyof O[K1]>(
    o: O, k1: K1, k2: K2
): O[K1][K2];
function getDeepVal<
    O extends DeepObj, K1 extends keyof O, K2 extends keyof O[K1],
    K3 extends keyof O[K1][K2]>(o: O, k1: K1, k2: K2, k3: K3): O[K1][K2][K3];
// the next one might not be needed if you never intend to call both at once
// as in "resultNotSure"
function getDeepVal<
    O extends DeepObj, K1 extends keyof O, K2 extends keyof O[K1],
    K3 extends keyof O[K1][K2]>(o: O, k1: K1, k2: K2, k3?: K3): O[K1][K2][K3] | O[K1][K2];

// Implementation:
function getDeepVal<
    O extends DeepObj, K1 extends keyof O, K2 extends keyof O[K1],
    K3 extends keyof O[K1][K2]>(o: O, k1: K1, k2: K2, k3?: K3) {
    return k3 ? o[k1][k2][k3] : o[k1][k2]
}
//调用签名
函数getDeepVal(
o:o,k1:k1,k2:k2
):O[K1][K2];
函数getDeepVal<
O扩展DeepObj,K1扩展keyof,K2扩展keyof[K1],
K3扩展了O[K1][K2]>(O:O,K1:K1,K2:K2,K3:K3):O[K1][K2][K3];
//如果您从未打算同时调用这两个,那么可能不需要下一个
//如“resultNotSure”
函数getDeepVal<
O扩展DeepObj,K1扩展keyof,K2扩展keyof[K1],
K3扩展了O[K1][K2]>(O:O,K1:K1,K2:K2,K3?:K3):O[K1][K2][K3]| O[K1][K2];
//实施:
函数getDeepVal<
O扩展DeepObj,K1扩展keyof,K2扩展keyof[K1],
K3扩展了O[K1][K2]>(O:O,K1:K1,K2:K2,K3?:K3){
返回k3?o[k1][k2][k3]:o[k1][k2]
}
这是更多的签名,但它的行为与条件类型方法相同,而且它可能对阅读它的人更有意义,因为它不需要太多的类型转换。让我们确保它工作正常:

const resultTwoLevels = getDeepVal(obj, 'a', 'b') // { c: string; }
const resultThreeLevels = getDeepVal(obj, 'a', 'b', 'c') // string
const resultNotSure = getDeepVal(obj, 'a', 'b',
    Math.random() < 0.5 ? 'c' : void 0); // string | { c: string; }
constresulttwolevels=getDeepVal(obj,'a','b')/{c:string;}
const resultthreevels=getDeepVal(obj,'a','b','c')//字符串
const resultNotSure=getDeepVal(对象'a','b',',
Math.random()字符串|{c:string;}
也很好。好的,希望能有帮助。祝你好运


谢谢您,先生!我对你的回答感激不尽。这个休息的小把戏太棒了。
const resultTwoLevels = getDeepVal(obj, 'a', 'b') // { c: string; }
const resultThreeLevels = getDeepVal(obj, 'a', 'b', 'c') // string
const resultNotSure = getDeepVal(obj, 'a', 'b',
    Math.random() < 0.5 ? 'c' : void 0); // string | { c: string; }