我正在try 编写一个函数,该函数接受一个描述符对象,并使用推断的类型信息返回一个不同的对象,该对象具有一个具有强类型的良好API.

我遇到的问题是,如果嵌套属性被限制为联合类型,则TypeScrip似乎只能推断它们的类型.如果它们被限制为基元类型(例如string),则它们将保持该基元类型,而不会缩小到传入的实际文字.

type ChildType = 'foo' | 'bar' | 'baz';

type ChildDescriptor<
    TName extends string = string,
    TType extends ChildType = ChildType,
> = {
    name: TName;
    type: TType;
};

type ParentDescriptor<
    TSomeProp extends string,
    TChildren extends ChildDescriptor[],
> = {
    someProp: TSomeProp;
    children: [...TChildren];
};

// Identity function so we can observe what types are inferred
const identity = <
    TSomeProp extends string,
    TChildren extends ChildDescriptor[],
>(
    descriptor: ParentDescriptor<TSomeProp, TChildren>,
) => descriptor;

const inferredValue = identity({
    someProp: 'I get inferred',
    children: [
        {
            name: 'I don\'t get inferred',
            type: 'foo',
        },
        {
            name: 'I don\'t get inferred either',
            type: 'baz',
        }
    ],
});

const [childA, childB] = inferredValue.children;

Playground

在上面的示例中,inferredValuechildren属性被正确地推断为元组,并且子对象的someProp属性和type属性被缩小到我想要的特定文本类型.但是,子对象的name属性保持为string的类型,我不知道为什么.

我可以通过在每个没有缩小范围的值之后添加as const来解决这个问题,或者甚至在每个子对象之后添加它.由于元组的原因,它不能工作在比这更低的嵌套级别,它变成只读的,然后很难在带有泛型的API中使用它.在任何情况下,我都希望完全避免这种解决方案,因为添加类型断言感觉像是一种黑客行为,而不是真正的解决方案.

最后,这些描述符对象将使用我将构建的另一款软件生成,因此,如果有必要,我将使用解决方法.我只是想看看有没有其他我看不到的解决方案.

我正在工作的环境被限制在TypeScrip 4.7.4,所以我必须坚持使用该版本中提供的功能.

推荐答案

以下是如何做到这一点,如果您不想使用as const,并将数组设置为readonly-

type Cast<A, B> = A extends B ? A : B;

type Narrowable = string | number | bigint | boolean;

type Narrow<A> = Cast<
  A,
  [] | (A extends Narrowable ? A : never) | { [K in keyof A]: Narrow<A[K]> }
>;

type ChildType = 'foo' | 'bar' | 'baz';

type ChildDescriptor<
  TName extends string = string,
  TType extends ChildType = ChildType,
> = {
  name: TName;
  type: TType;
};

type ParentDescriptor<
  TSomeProp extends string,
  TChildren extends ChildDescriptor[],
> = {
  someProp: TSomeProp;
  children: [...TChildren];
};

// Identity function so we can observe what types are inferred
const identity = <
  TSomeProp extends string,
  TChildren extends ChildDescriptor[],
>(
  descriptor: ParentDescriptor<TSomeProp, Narrow<TChildren>>,
) => descriptor;

const inferredValue = identity({
  //    ^? const inferredValue: ParentDescriptor<"I get inferred", [{ name: "I don't get inferred"; type: "foo"; }, { name: "I don...
  someProp: 'I get inferred',
  children: [
    {
      name: "I don't get inferred",
      type: 'foo',
    },
    {
      name: "I don't get inferred either",
      type: 'baz',
    },
  ],
});

const [
  childA,
  //^? const childA: { name: "I don't get inferred"; type: "foo"; }
  childB,
  //^? const childB: { name: "I don't get inferred either"; type: "baz"; }
] = inferredValue.children;

这是一张Playground link美元.

解释

这样做的原因是因为TypeScrip编译器扫描输入,使用它的算法,它首先扫描非上下文的泛型,然后扫描上下文的泛型,你可以阅读更多关于它的herehere.

因此,如果我们使用Narrow实用程序,则TypeScrip被迫运行分析以捕获其上的类型,由于Array实际上是一个对象,因此我们详尽地映射到它的所有属性,因为您可以看到这是一个递归实用程序.This个例子会让你对它有更多的了解-

type A = { [K in keyof ["some", "things"]]: ["some", "things"][K] }
//   ^? type A= { [x: number]: "some" | "things"; 0: “some"; 1: "things"; length: 2; toString: () => string; toLocaleString: (...

中间的Cast实用程序是所有魔术发生的地方,它实际上是一个技巧,让编译器通过类型B推断A的类型,本质上,如果你添加两个泛型并约束其中一个,然后在另一个约束泛型中使用它,编译器就会推断出as const,for example这样的类型-

type Some = number | string;

function getValues<N extends Some, K extends Record<keyof K, N>>(arg: K) {
    return arg;
}

const value = getValues({ a: "something", b: "another thing" });
//    ^? const value: { a: "something"; b: “another thing”; }

Typescript相关问答推荐

如何为ViewContainerRef加载的Angular组件实现CanDeactivate保护?

当我点击外部按钮时,如何打开Html Select 选项菜单?

如何将联合文字类型限制为文字类型之一

无法从Chrome清除还原的初始状态数据

为什么我的一个合成函数不能推断类型?

记录键的泛型类型

在HighChats列时间序列图中,十字准线未按预期工作

如何通过TypeScrip中的函数防止映射类型中未能通过复杂推理的any值

从泛型类型引用推断的文本类型

如何合并两个泛型对象并获得完整的类型安全结果?

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

React router 6(数据路由)使用数据重定向

如何获取受类型脚本泛型约束的有效输入参数

当数组类型被扩展并提供标准数组时,TypeScrip不会抱怨

在TypeScript中,如何通过一系列过滤器缩小类型?

可以用任意类型实例化,该类型可能与

换行TypeScript';s具有泛型类型的推断数据类型

TypeScript是否不知道其他函数内部的类型判断?

覆盖高阶组件中的 prop 时的泛型推理

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