typescript 排除基本类型列表

2nbm6dog  于 2022-12-19  发布在  TypeScript
关注(0)|答案(2)|浏览(205)

是否可以在TypeScript中使用以下签名实现一个从现有类型中删除/排除基本类型的过滤器函数(通过正确的类型重新转换)?-

type basicType = 'string' | 'number' | 'boolean' | 'bigint';

function removeByType<T>(input: T[], ...types: basicType[]): Exclude<T, ?>[] {
   // actual data filtering (irrelevant for this question)
}

我不担心确切的签名,只要它可以像这样使用(或类似):

const input = [1, 2, 3, 'four', false]; //=> Array<number | string | boolean>

const res = removeByType(input, 'string', 'boolean'); //=> Array<number>

如果这是不可能的,那么什么是下一个最好的事情呢?
到目前为止,我只能通过以下重新声明使它只适用于单值参数:

function removeByType<T>(input: T[], t: 'string'): Exclude<T, string>[];
function removeByType<T>(input: T[], t: 'number'): Exclude<T, number>[];
function removeByType<T>(input: T[], t: 'boolean'): Exclude<T, boolean>[];
function removeByType<T>(input: T[], t: 'bigint'): Exclude<T, bigint>[];

我正在努力弄清楚如何让它工作的名单,或者如果它是在所有可能的...

    • 更新**

最终的解决方案是here,因为它是为iter-ops库的自定义操作符设计的。非常感谢Jared Smith的回答!

xdyibdwo

xdyibdwo1#

这个问题的问题是类型没有术语级别的表示(例如,运行时typeof操作符给出的值只是字符串),而且你不能像Haskell类型类元编程那样从类型-〉术语开始,如果我们稍微调整一下签名,在类型级别实现这一点并不难。在术语级别实现它涉及到......?所以实现逻辑不一定是无关紧要的。
我在下面用一个map实现了它,用实际类型和自由强制转换来Map原语的字符串类型。也许其他人会想出一个更聪明的解决方案来保护更多的类型安全。

type PrimitiveMap = {
    string: string;
    number: number;
    boolean: boolean;
    undefined: undefined;
    symbol: symbol;
    // etc
}

type PrimitivesAsStrings = keyof PrimitiveMap;
type Primitives = PrimitiveMap[PrimitivesAsStrings]

function removeByType<
    T extends Primitives,
    R extends PrimitivesAsStrings
>(input: T[], ...exclusions: R[]): Exclude<T, PrimitiveMap[R]>[] {
    return input.filter((item) => exclusions.includes(typeof item as R)) as any;
}

const test = [1, 2, true, 'hi'];
const onlyNum = removeByType(test, 'boolean', 'string'); // number[]

Playground
最后一点提醒:我通常称这样的代码为“聪明”,但这并不一定是赞美。仔细权衡在代码库中包含这样的代码的利弊,以及你是否得到了足够的安全性来权衡复杂性。类型系统的诡计总是有趣的,但只是偶尔值得为生产代码IMO。

cl25kdpy

cl25kdpy2#

下面是我在玩它的时候自己创造的半答案:

type basicType<T> = T extends 'string' ? string :
    T extends 'number' ? number :
        T extends 'boolean' ? boolean :
            T extends 'bigint' ? bigint :
                never;

function removeByType<T, A>(input: T[], t1: keyof A): Exclude<T, basicType<typeof t1>>[];
function removeByType<T, A, B>(input: T[], t1: keyof A, t2: keyof B): Exclude<T, basicType<typeof t1 | typeof t2>>[];
function removeByType<T, A, B, C>(input: T[], t1: keyof A, t2: keyof B, t3: keyof C): Exclude<T, basicType<typeof t1 | typeof t2 | typeof t3>>[];
function removeByType<T, A, B, C, D>(input: T[], t1: keyof A, t2: keyof B, t3: keyof C, t4: keyof D): Exclude<T, basicType<typeof t1 | typeof t2 | typeof t3 | typeof t4>>[];

function removeByType<T, A>(input: T[], ...t: (keyof A)[]): Exclude<T, any> {
    return input.filter(a => {
        return t.indexOf(typeof a as any) < 0;
    }) as any;
}

它工作正常,但它的问题-值没有强制执行,也就是说,你可以传入任何像bla-bla字符串,TS不会抱怨。
这就是为什么Jared Smith的早期解决方案更好的原因。除非,这个解决方案可以很容易地修复,以某种方式?

相关问题