您好,我创建了一个插件和一个调用Plugin操作的函数.


export class Plugin {
    actions = {
        'join': ({ groupId }: { groupId: string }) => {

        },
        'addPoint': ({ point }: { point: number }) => {

        }
    }
}


export type PluginParams<T> = T extends {
    actions: Record<infer X, (param: infer Params) => any>
} ? {
    action: X,
    data: Params, // how do i do mapping type  ?
} : never;

function callPlugin<T>(v: PluginParams<T>) {
    console.log(v);
}


callPlugin<Plugin>({
    action: 'addPoint',
    data: {
        // it has an error here how do i fix that 
        // when action is `addPoint` it should only require point.
        point: 100
    }
})


我可以从插件中推断参数,但我不知道如何将它们与映射类型一起使用.我只想让它将动作名称与动作函数中的参数进行映射

我已经在堆栈溢出上判断了所有类似的问题,但我仍然找不到解决方案.

谢谢.

TS Playground

推荐答案

由于您要在调用callPlugin()(如callPlugin<Plugin>({⋯}))时手动指定generic类型参数,因此需要在此处定义PluginParams<T>,以便PluginParams<Plugin>成为表单可接受输入的discriminated union:

type Demo = PluginParams<Plugin>
/* type Demo = {
    action: "join";
    data: {
        groupId: string;
    };
} | {
    action: "addPoint";
    data: {
        point: number;
    };
} */

有一种方法可以做到这一点:

type PluginParams<T extends {
    actions: Record<keyof T["actions"], (p: any) => any>
}> = { [K in keyof T["actions"]]:
    { action: K, data: T["actions"][K] extends (p: infer P) => any ? P : never }
}[keyof T["actions"]]

我是constraining,T,所以它有一个Record<keyof T["actions"], (p: any) => any>类型的actions属性,所以它只接受像Plugin这样的东西.请注意,您可以使用string来代替递归keyof T["actions"],但有时这并不能很好地处理接口类型(请参见Subtype of Record<string, X> without the index signature).

然后,我们所做的就是对T["actions"]的属性进行map,并将它们转换为具有适当类型的actiondata属性的对象,然后将其与键的并集进行index into,以得到并集.这种形式{[P in K]: F<P>}[K]的索引映射类型使得K(例如,K1 | K2 | K3)中的并集在输出(例如,F<K1> | F<K2> | F<K3>)中产生并集,被称为"分布式对象类型",并在microsoft/TypeScript#47F<K1> | F<K2> | F<K3>中描述.

如果您将callPlugin定义为接受PluginParams<T>,则您可以验证PluginParams<Plugin>是否成为所需的区分联合,以及您的呼叫是否按预期工作:

function callPlugin<T extends {
    actions: Record<keyof T["actions"], (p: any) => any>
}>(v: PluginParams<T>) {
    console.log(v);
}

callPlugin<Plugin>({
    action: 'addPoint',
    data: {
        point: 100
    }
})

callPlugin<Plugin>({
    action: 'join',
    data: {
        point: 100 // error! 
        // Type '{ point: number; }' is not 
        // assignable to type '{ groupId: string; }'.
    }
})

callPlugin<Plugin>({
    action: 'join',
    data: {
        point: 100 // error! 
        // Type '{ point: number; }' is not 
        // assignable to type '{ groupId: string; }'.
    }
})

请注意,这是一种编写和调用泛型函数的非标准方式.它要求调用方手动指定类型参数,这通常是推断出来的.我希望您的callPlugin()函数也应该接受plugin参数,从中可以推断T,或者您应该重构,以便在任何人调用callPlugin()之前省go 所有泛型内容,这样就没有人需要记住手动指定类型参数.从技术上讲,这超出了所问问题的范围,因此我不会深入探讨这一问题,只是要注意您的代码可以被调用者正确地使用.

Playground link to code

Typescript相关问答推荐

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

ReturnType此索引方法与点表示法之间的差异

使某些类型的字段变为可选字段'

如果存在ts—mock—imports,我们是否需要typescpt中的IoC容器

如何使用泛型类型访问对象

如何为父类构造函数中的修饰属性赋值?

使用打字Angular 中的通用数据创建状态管理

如何在已知基类的任何子类上使用`extends`?

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

如何在省略一个参数的情况下从函数类型中提取参数类型?

编译TypeScrip项目的一部分导致错误

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

三重融合视角与正交阵

Karma Angular Unit测试如何创建未解析的promise并在单个测试中解决它,以判断当时的代码是否只在解决后运行

Typescript 从泛型推断类型

元素隐式具有any类型,因为string类型的表达式不能用于索引类型{Categories: Element;管理员:元素; }'

Typescript 确保通用对象不包含属性

如何在没有空接口的情况下实现求和类型功能?

Mongodb TypeError:类继承 events_1.EventEmitter 不是对象或 null

从接口推断模板参数?