我正在try 类似以下的方法:

type Node =
    | { type: 'Group'; children: Node[] }
    | { type: 'Obj'; payload: number };

class Group {
    readonly type = 'Group';
    constructor(public n: Node[]) { }
}

class Obj {
    readonly type = 'Obj';
    constructor(public p: number) { }
}

type NodeMappings =
    { Group: Group; Obj: Obj };

function createNode<T extends Node>(node: T): NodeMappings[T['type']] {
    switch (node.type) {
        case 'Group':
            return new Group(node.children); // error!
        case 'Obj':
            return new Obj(node.payload); // error!
    }
}

有没有一种方法可以实现与createNode函数中映射的所有类型一起工作的给定代码(这里是通过switch ... case完成的)?这是打字脚本中的错误吗?

推荐答案

编译器不能仅仅看到NodeNodeMappings之间的抽象关系来验证createNode()是否正确实现.

如果你想以编译器可以理解的方式来表达这种关系,你需要按照microsoft/TypeScript#47109中的步骤来明确地做到这一点.你需要一个"base"类型,它看起来像是从每个type属性到相关Node成员的其余部分的键值映射:

type NodeMap = { [N in Node as N['type']]:
    { [P in keyof N as P extends "type" ? never : P]: N[P] }
}

这相当于

// type NodeMap = { 
//   Group: { children: Node[]; }; 
//   Obj: { payload: number; };
// }

然后,所有操作都应该表示为该基本类型,并使用generic键将indexes表示为该类型,或者将通用索引表示为该类型上的mapped type.

我们可以将Node重新创建为这样一个索引映射类型(在Microsoft/TypeScript#47109中称为distributive object type):

type MyNode<K extends keyof NodeMap = keyof NodeMap> =
    { [P in K]: { type: P } & NodeMap[P] }[K]

然后按照通用索引KMyNode<K>写出createNode():

function createNode<K extends keyof NodeMap>(node: MyNode<K>) {
    const m: { [K in keyof NodeMappings]: (n: MyNode<K>) => NodeMappings[K] } = {
        Group(n) { return new Group(n.children) },
        Obj(n) { return new Obj(n.payload) }
    };
    return m[node.type](node); // okay
}

请注意,我们不得不放弃使用switch/case的基于control-flow的实现.您不能只判断node.type并期望K会受到影响,至少在microsoft/TypeScript#33014实现之前是这样的.因此,我们不这样做,而是创建一个对象,该对象的方法与node.type同名,并且接受相应的 node 作为输入.也就是说,我们编写了一个将MyNode<K>映射到NodeMappings[K]的对象.

这是可行的;调用m[node.type](node)实际上与您的switch/case版本相同,但我们使用属性查找来区分情况.

让我们确保它仍然适用于呼叫者:

function test() {
    const n = createNode({ type: 'Obj', payload: 8 });
    //    ^? const n: Obj
}

看起来不错 K被推断为"Obj",因此返回类型为Obj.

Playground link to code

Typescript相关问答推荐

如何根据参数的值缩小函数内的签名范围?

如果我有对象的Typescript类型,如何使用其值的类型?

对于使用另一个对象键和值类型的对象使用TS Generics

在NextJS中获得Spotify Oauth,但不工作'

如何编写一个类型脚本函数,将一个对象映射(转换)为另一个对象并推断返回类型?

TypeScrip原始字符串没有方法

为什么在回调函数的参数中不使用类型保护?

如何消除在Next.js中使用Reaction上下文的延迟?

如何在Vue中使用Enum作为传递属性的键类型?

布局组件中的Angular 17命名路由出口

如何将对象数组中的键映射到另一种对象类型的键?

TYPE FOR ARRAY,其中每个泛型元素是单独的键类型对,然后在该数组上循环

如何避免从引用<;字符串&>到引用<;字符串|未定义&>;的不安全赋值?

TypeScrip-如何自动推断UNION变量的类型

Cypress-验证别名的存在或将别名中的文本与';if';语句中的字符串进行比较

打字脚本错误:`不扩展[Type]`,而不是直接使用`[Type]`

如何以类型安全的方式指定键的常量列表,并从该列表派生所挑选的接口?

使用泛型以自引用方式静态键入记录的键

Typescript循环函数引用

将带有额外id属性的Rust struct 展平回TypeScript单个对象,以返回wasm-bindgen函数