我有一个帮助器函数,它包装一些对象,并根据它是查询函数还是变异函数来修改其所有键.

我想要编写一个类型脚本声明,以确保对象的所有键都在两个参数中的任何一个中指定.

我目前拥有的是:

function createService<
    T,
    Q extends keyof T,
    M extends Exclude<keyof T, Q>,
>(obj: T, queries: readonly Q[], mutations: readonly M[]) {}

这确实验证了密钥只在两个参数中的一个中, 但我想不出如何验证所有密钥是否都存在.

用法示例:

interface Test {
  a: () => void;
  b: () => void;
}
const obj: Test = { a: () => {}, b: () => {} };

createService(obj, ["a"], ["b"]); // OK
createService(obj, ["a", "b"], []); // OK

createService(obj, ["a", "b"], ["b"]); // Error, "b" is set twice
createService(obj, ["a"], []); // No Error but should be because "b" is not set
createService(obj, [], ["b"]); // No Error but should be because "a" is not set

我try 了类似以下的方法:但这不能编译.

function createService<
    T,
    Q extends keyof T,
    M extends Exclude<keyof T, Q>,
    CHECK extends never = Exclude<keyof T, Q | M>, // Type 'string' is not assignable to type 'never'
>(obj: T, queries: readonly Q[], mutations: readonly M[]) {}

Playground link to example code

这是否有可能在Typescript 中得到验证?

推荐答案

您可以采取的一种方法是将constraint也添加到T,这样就可以有效地禁止除QM之外的任何键的属性(使用the impossible never type:

function createService<
    T extends Record<Exclude<keyof T, Q | M>, never>,
    Q extends keyof T,
    M extends Exclude<keyof T, Q>
>(obj: T, queries: readonly Q[], mutations: readonly M[]) { }


interface Test {
    a: () => void;
    b: () => void;
}
const obj: Test = { a: () => { }, b: () => { } };
    
createService(obj, ["a"], ["b"]); // OK
createService(obj, ["a", "b"], []); // OK

createService(obj, ["a", "b"], ["b"]); // Error
createService(obj, ["a"], []); // Error
createService(obj, [], ["b"]); // Error

现在,这起到了预期的作用.

请注意,这并没有考虑到所有可能的情况.TypeScrip本身并不代表exhaustive arrays的概念.类型为(T1 | T2 | T3)[]的数组不一定都有类型为T1T2T3的元素.它根本不能保证有任何元素.所以有could个人这样做:

const aArr: "a"[] = [];
const bArr: "b"[] = [];
createService(obj, aArr, bArr);

想必这不是您关心的用例.如果是,那么唯一可能的方法是列举每一种可能的可接受的tuple输入:

type AllPossiblePartialTuples<T, U = T> =
    [T] extends [never] ? readonly [] : T extends any ?
    readonly [T, ...AllPossiblePartialTuples<Exclude<U, T>>] | 
    AllPossiblePartialTuples<Exclude<U, T>>
    : never;

type AllPossibleTuples<T, U = T> =
    [T] extends [never] ? readonly [] : T extends any ?
    readonly [T, ...AllPossibleTuples<Exclude<U, T>>] : never;

declare function createService<
    T extends object,
    const Q extends AllPossiblePartialTuples<keyof T>
>(
    obj: T,
    queries: Q,
    mutations: AllPossibleTuples<
        Exclude<keyof T, Q extends readonly (infer V)[] ? V : never>
    >
): void;

const obj: Test = { a: () => { }, b: () => { } };

createService(obj, ["a"], ["b"]); // OK
createService(obj, ["a", "b"], []); // OK

createService(obj, ["a", "b"], ["b"]); // Error
createService(obj, ["a"], []); // Error
createService(obj, [], ["b"]); // Error

但只有在T只有少量密钥的情况下,这才是可信的.即使是数量不多的键,这些键的所有可能排列的并集也非常大,并且会导致性能问题或关于复杂性限制的编译器错误,或者两者兼而有之:

// okay-ish
createService(
    { a: 0, b: 1, c: 2, d: 3, e: 4, f: 5 },
    ['a', 'c', 'e'],
    ['b', 'd', 'f']
);

// 🔥💻🔥 COMPILER EATS UP CPU RESOURCES
createService(
    { a: 0, b: 1, c: 2, d: 3, e: 4, f: 5, g: 6, h:7},
    ['a', 'c', 'e', 'g'],
    ['b', 'd', 'f', 'h']
);

// ☠🔥💻🔥☠ EVENTUALLY ISSUES AN ERROR ABOUT COMPLEXITY
createService(
    { a: 0, b: 1, c: 2, d: 3, e: 4, f: 5, g: 6, h:7, i: 8, j: 9, k: 10},
    ['a', 'c', 'e', 'g', 'i', 'k'],
    ['b', 'd', 'f', 'h', 'j']
);

因此,我可能建议您使用第一种方法,除非您有非常特殊的用例.

Playground link to code

Typescript相关问答推荐

TypScript中的算法运算式

TypScript手册中的never[]参数类型是什么意思?

将值添加到具有不同类型的对象的元素

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

有没有办法解决相互影响的路由之间的合并?

在TypeScrip的数组中判断模式类型

匿名静态类推断组成不正确推断

打印脚本中的动态函数重载

TypeScrip:逐个 case Select 退出noUncheck kedIndexedAccess

我可以使用TypeScrip从字符串词典/记录中填充强类型的环境对象吗?

TS如何不立即将表达式转换为完全不可变?

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

判断映射类型内的键值的条件类型

对有效枚举值的常量字符串进行常规判断

如何在具有嵌套数组的接口中允许新属性

打字:注解`是字符串`而不是`布尔`?

为什么类方法参数可以比接口参数窄

具有枚举泛型类型的 Typescript 对象数组

React-Router V6 中的递归好友路由

TS 条件类型无法识别条件参数