嵌套通用 TS - 将参数设置为“and”而不是“or”

问题描述 投票:0回答:1

我正在尝试在我的项目中创建人类功能挂钩 我使用泛型来决定并保持每个可用函数的挂钩 我不想设置所有潜在的功能,只有当我们添加钩子时我才想在尚未声明的情况下声明它 但由于某种原因,在钩子更改中,TS 乘以参数为“and”而不是“or” 为了方便检查,我设置了一个TS游乐场

我认为这是我对打字稿缺乏了解 将不胜感激任何帮助

interface HumanFunctionTypeMap {
    eat: {
        name: string;
        diet: boolean;
        vegan: boolean;
    };

    drink: Array<{
        diet: boolean;
        regular: boolean;
        soda: boolean;
    }>;

    sleep: {
        dream: boolean;
        snooze: boolean;
    };
}

type HumanHookName = keyof HumanFunctionTypeMap;

type HumanHook = {
    [K in HumanHookName]?: {
        hooks: Array<(args: HumanFunctionTypeMap[K]) => void>;
        handler: (args: HumanFunctionTypeMap[K]) => void;
    };
};

interface RegisterHumanHook {
    <K extends HumanHookName>(name: K, hook: (args: HumanFunctionTypeMap[K]) => void): void;
}

const humanHooks: HumanHook = {};

export const registerHumanHook: RegisterHumanHook = (name, hook) => {
    if (!humanHooks[name]) {

        humanHooks[name] = {
            hooks: [],
            handler: args => {
                humanHooks[name]?.hooks.forEach(hook => hook(args));
            }
        };

    }

    humanHooks[name]?.hooks?.push(hook);
};
Type '{ hooks: never[]; handler: (args: HumanFunctionTypeMap[K]) => void; }' is not assignable to type 'HumanHook[K]'.
  Type '{ hooks: never[]; handler: (args: HumanFunctionTypeMap[K]) => void; }' is not assignable to type '{ hooks: ((args: { name: string; diet: boolean; vegan: boolean; }) => void)[]; handler: (args: { name: string; diet: boolean; vegan: boolean; }) => void; } & { hooks: ((args: { diet: boolean; regular: boolean; soda: boolean; }[]) => void)[]; handler: (args: { ...; }[]) => void; } & { ...; }'.
    Type '{ hooks: never[]; handler: (args: HumanFunctionTypeMap[K]) => void; }' is not assignable to type '{ hooks: ((args: { name: string; diet: boolean; vegan: boolean; }) => void)[]; handler: (args: { name: string; diet: boolean; vegan: boolean; }) => void; }'.
      Types of property 'handler' are incompatible.
        Type '(args: HumanFunctionTypeMap[K]) => void' is not assignable to type '(args: { name: string; diet: boolean; vegan: boolean; }) => void'.
          Types of parameters 'args' and 'args' are incompatible.
            Type '{ name: string; diet: boolean; vegan: boolean; }' is not assignable to type 'HumanFunctionTypeMap[K]'.
              Type '{ name: string; diet: boolean; vegan: boolean; }' is not assignable to type '{ name: string; diet: boolean; vegan: boolean; } & { diet: boolean; regular: boolean; soda: boolean; }[] & { dream: boolean; snooze: boolean; }'.
                Type '{ name: string; diet: boolean; vegan: boolean; }' is missing the following properties from type '{ diet: boolean; regular: boolean; soda: boolean; }[]': length, pop, push, concat, and 26 more.

我尝试设置

humanHooks[name as HumanHookName]
但后来我失去了 TS 的好处,因为
args => args:any
而不是我们使用的类型

我尝试过改变结构,但又回到了同样的问题

typescript generics types
1个回答
0
投票

这与 microsoft/TypeScript#47109 中描述的处理相关类型的方法非常接近。

请注意,您的类型确实没有任何问题,只是 TypeScript 无法遵循逻辑。解决方案涉及重构以使用本质上等效的类型,但其形式是可以理解的。


我看到的唯一区别是您的

HumanHook
类型是单个对象类型,因此
HumanHook[K]
只是一个普通的索引访问类型,但 TypeScript 显然想要使用 分布式对象类型 (如 ms/ 中所创造的) TS#47109) 相反。如果
K
是单个键,则没有区别,但在存在 unions 的情况下,编译器会担心交叉相关,为了安全起见,它需要一个 intersection。您可以阅读 microsoft/TypeScript#30769 了解为什么会出现这种情况,但我们希望避免这种情况。

因此,我们需要使用

{[P in HumanHookName]?: F<P>}[K]
来使其成为分布式对象类型,而不是
HumanHook[K]
(根据您的定义,它是
F<>
,并使用
{[P in K]?: F<P>}[K]
作为简写)。我们可以通过重写您的
HumanHook
以采用 generic 类型参数
K
来实现这一点,该参数是 constrained
HumanHookName
。我们甚至可以给它HumanHookName的默认类型参数
,这样当你写没有泛型的
HumanHook
时,它就相当于你的旧版本。像这样:

type HumanHook<K extends HumanHookName = HumanHookName> = { [P in K]?: { hooks: Array<(args: HumanFunctionTypeMap[P]) => void>; handler: (args: HumanFunctionTypeMap[P]) => void; }; };
所以现在

HumanHook<K>

看起来很像
Pick<HumanHook, K>
(使用
Pick
实用程序类型
),除了编译器在接下来的内容中理解前者而不是后者:

const humanHooks: HumanHook = {}; export const registerHumanHook: RegisterHumanHook = (name, hook) => { const h: HumanHook<typeof name> = humanHooks; // const h: HumanHook<K> if (!h[name]) { h[name] = { hooks: [], handler: args => { h[name]?.hooks.forEach(hook => hook(args)); } }; } h[name]?.hooks?.push(hook); };
我们不使用 

humanHooks

 类型的 
HumanHook
,而是使用 
h
 类型的 
HumanHook<K>
(除非您的实现使用其参数的 
上下文类型,因此 K
 是匿名的。我们可以使用 
typeof name
 来恢复它,所以类型是 
HumanHook<typeof K>
。或者我们可以忘记上下文类型并为您的实现提供完整注释的调用签名。无论哪种方式)。

请注意,对于每个

K

HumanHook<K>
 都是 
HumanHook
 的超类型,因此可以安全地扩大。也就是说,
const h: HumanHook<typeof name> = humanHooks;
是允许的。此后,
h[name]
的类型被视为
HumanHook<K>[K]
,这是与
F<K> | undefined
等效的分布式对象类型。现在一切都按预期进行。

Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.