考虑中间可选键来推断嵌套值类型

tox*_*oxi 3 type-inference typescript mapped-types conditional-types

我正在尝试定义帮助器类型来确定嵌套对象值的类型,同时还考虑任何可选的父键,例如在这样的(或更深的)结构中:

type Foo = { a: { b?: number; } };

type Foo2 = { a?: { b: number } };
Run Code Online (Sandbox Code Playgroud)

对于我而言,类型bFooFoo2应被推断为number | undefined。在Foo2b不是可有可无的本身,而是因为a,对于我的查找目的b现在必须是可选的太多......这么多的情况下。

使用这些辅助类型(从更大的集合中提取)作为构建块:

type Keys<T> = keyof Required<T>;

type IsOpt<T> = T extends undefined ? true : never;

type HasOptKey1<T, A> = A extends Keys<T> ? IsOpt<T[A]> : never;

type HasOptKey2<T, A, B> = A extends Keys<T>
    ? IsOpt<T[A]> extends never
        ? HasOptKey1<T[A], B>
        : true
    : never;

type Val1<T, A> = A extends Keys<T> ? T[A] : never;

type Val2<T, A, B> = A extends Keys<T> ? Val1<Required<T>[A], B> : never;
Run Code Online (Sandbox Code Playgroud)

充分利用这些,我们得到:

type F1 = HasOptKey1<Foo, "a">; // never - CORRECT!
type F2 = HasOptKey1<Foo2, "a">; // true - CORRECT!
type F3 = HasOptKey2<Foo, "a", "b">; // true - CORRECT!
type F4 = HasOptKey2<Foo2, "a", "b">; // true - CORRECT!

// infer type of `a` in Foo
type A1 = HasOptKey1<Foo, "a"> extends never
  ? Val1<Foo, "a">
  : Val1<Foo, "a"> | undefined;
// { b: number | undefined; } - CORRECT!

// infer type of `a` in Foo2
type A2 = HasOptKey1<Foo2, "a"> extends never
  ? Val1<Foo2, "a">
  : Val1<Foo2, "a"> | undefined;
// { b: number } | undefined - CORRECT!

// infer type of `b` in Foo
type B1 = HasOptKey2<Foo, "a", "b"> extends never
    ? Val2<Foo, "a", "b">
  : Val2<Foo, "a", "b"> | undefined;
// number | undefined - CORRECT!

// infer type of `b` in Foo2
type B2 = HasOptKey2<Foo2, "a", "b"> extends never
    ? Val2<Foo2, "a", "b">
  : Val2<Foo2, "a", "b"> | undefined;
// number | undefined - CORRECT!
Run Code Online (Sandbox Code Playgroud)

为了避免这些重复的条件,我想使用另一种辅助类型:

// helper type w/ same logic as used for A1/A2/B1/B2 conditionals
type OptVal<PRED, RES> = PRED extends never ? RES : RES | undefined;

// applied
type OptVal1<T, A> = OptVal<HasOptKey1<T, A>, Val1<T, A>>;

type OptVal2<T, A, B> = OptVal<HasOptKey2<T, A, B>, Val2<T, A, B>>;
Run Code Online (Sandbox Code Playgroud)

然而,即使它似乎适用于 4 种情况中的 3 种,但A3被错误地推断为never,我不明白为什么:

type A3 = OptVal1<Foo, "a">;
// never - WHHHYYY??? (should be same as A1!) <-----

type A4 = OptVal1<Foo2, "a">;
// { b: number } | undefined - CORRECT! (same as A2)

type B3 = OptVal2<Foo, "a", "b">; // number | undefined - CORRECT!

type B4 = OptVal2<Foo2, "a","b">; // number | undefined - CORRECT!
Run Code Online (Sandbox Code Playgroud)

游乐场链接

jca*_*alz 5

有可能是完成你想要做什么其他的方法,但你面对眼前的问题是你不小心发布您的条件类型中的定义OptVal。由于PRED是类型参数,条件检查PRED extends never ? RES : RES | undefined最终将拆分PRED为其联合成员,评估每个成员的条件,然后将结果重新联合在一起。你的问题是什么时候PREDnever。您可能不会将其never视为联合类型,但为了一致性起见,编译器将其视为“空联合”,并且输出也将是空联合,即never.

关闭分布式条件类型的最简单方法是采用裸类型参数PRED并将其“包裹”在单元素元组类型中,如下所示:

type OptVal<PRED, RES> = [PRED] extends [never] ? RES : RES | undefined;
Run Code Online (Sandbox Code Playgroud)

这将使您的案例按预期工作,我认为:

type A3 = OptVal1<Foo, "a">; // { b?: number | undefined; }
Run Code Online (Sandbox Code Playgroud)

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

Playground 链接到代码

  • 这是非常有道理的,感谢您提醒我并解释条件类型处理的这个特定方面!现在效果很好! (3认同)