此代码从CSV编写库简化而来:

(我现在使用的是UnionToIntersection助手:https://stackoverflow.com/a/50375286/163832)

type UnionToIntersection<U> =
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;

type Spec = 'string' | 'timestamp' | ['nullable', Spec];

type SpecToValue<T> = UnionToIntersection<
    T extends 'string' ? string :
    T extends 'timestamp' ? Date :
    T extends ['nullable', infer InnerT] ? SpecToValue<InnerT> | null :
    never
>;

declare function writeValue<T extends Spec>(spec: T, value: SpecToValue<T>): void;

function test(s1: 'string', s2: 'string' | 'timestamp', s3: ['nullable', 'string']) {
    writeValue(s1, 'hello');
    writeValue(s1, false); // error (good)
    writeValue(s1, null);  // error (good)

    writeValue(s2, 'hello'); // error (good)
    writeValue(s2, false);   // error (good)
    writeValue(s2, null);    // error (good)

    // Problem: SpecToValue<['nullable', 'string']> resolves to `never` instead of `string | null`.
    writeValue(s3, 'hello'); // ERROR (bad; I want this to be allowed)
    writeValue(s3, false);   // error (good)
    writeValue(s3, null);    // ERROR (bad; I want this to be allowed)

我认为问题是,UnionToIntersection号高速公路也会将SpecToValue<InnerT> | null号高速公路转换为十字路口,但我不想这样做.我想把它作为一个unions 保留下来.

有办法做到这一点吗?

Edit:在我的实际代码中,我将"简单"类型与"可空"类型分开;不确定这是否会对解决方案产生很大影响:

type SimpleSpec = 'string' | 'timestamp';
type Spec = SimpleSpec | ['nullable', SimpleSpec];

type SimpleSpecToValue<T> =
    T extends 'string' ? string :
    T extends 'timestamp' ? Date :
    never;
type SpecToValue<T> =
    T extends SimpleSpec ? SimpleSpecToValue<T> :
    T extends ['nullable', infer InnerT] ? (SpecToValue<InnerT> | null) : null

(不过,如果我必须把这些定义结合起来,这并不是交易的 destruct 者.)

推荐答案

您并不是真的想将UnionToIntersection<T>应用于SpecToValue的结果,因为这不是您试图将unions转换为intersections的级别.相反,您实际上希望为T的每个联合成员计算SpecToValue<T>,然后生成result的交集.

本质上,这意味着您希望应用SpecToValue<T> insideUnionToIntersection实用程序类型,而不是反之亦然:

type SpecToValue<T> =
  T extends 'string' ? string :
  T extends 'timestamp' ? Date :
  T extends ['nullable', infer InnerT] ? SpecToValue<InnerT> | null :
  never

type ContravariantSpecToValue<T> =
  (T extends any ? ((x: SpecToValue<T>) => void) : never) extends
  ((x: infer I) => void) ? I : never;

让我们测试一下:

type Combination = ContravariantSpecToValue<
  ['nullable', ['nullable', 'timestamp']] | 'timestamp'
>;
//type Combination = Date

看起来不错,结果是Date & (Date | null)(或Date)而不是Date & Date * null(或never).为了确保它适用于您的示例:

declare function writeValue<T extends Spec>(
  spec: T, value: ContravariantSpecToValue<T>): void;

function test(s1: 'string', s2: 'string' | 'timestamp', s3: ['nullable', 'string']) {
  writeValue(s1, 'hello'); // okay
  writeValue(s1, false); // error
  writeValue(s1, null);  // error

  writeValue(s2, 'hello'); // error
  writeValue(s2, false);   // error
  writeValue(s2, null);    // error

  writeValue(s3, 'hello'); // okay 
  writeValue(s3, false);   // error 
  writeValue(s3, null);    // error
}

也很好.

Playground link to code

Typescript相关问答推荐

如何将绑定到实例的类方法复制到类型脚本中的普通对象?

界面中这种类型的界面界面中的多态性

类型脚本`Return;`vs`Return unfined;`

如何在TypeScrip中从字符串联合类型中省略%1值

APIslice-CreateAPI的RTK打字错误

参数属性类型定义的REACT-RUTER-DOM中的加载器属性错误

MatTool提示在角垫工作台中不起作用

下载量怎么会超过下载量?

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

在组件卸载时try 使用useEffect清除状态时发生冲突.react +打字

是否可以通过映射类型将函数参数约束为预定义类型?

使用或属性编写无限嵌套的接口

是否可以在类型脚本中定义条件返回类型,而不在函数体中使用类型转换?

如何使用angular15取消选中位于其他组件中的复选框

基于属性值的条件类型

类型脚本中函数重载中的类型推断问题

两个接口的TypeScript并集,其中一个是可选的

Typescript 子类继承的方法允许参数中未定义的键

使用受保护的路由和入门来响应路由

如何为字符串模板文字创建类型保护