任意泛型的typescript映射

IT技术 javascript reactjs typescript generics
2021-05-18 20:19:59

我正在尝试定义两种类型,它们应该类似于:

export type IQuery<P, U>  = {
  request: string;
  params: (props: P, upsteam?: U) => object;
  key: (props: P, upstream?: U) => string;
  forceRequest: boolean;
  depends?: QueryMap
}

export type QueryMap = {
  [k: string]: IQuery
};

我想表达的约束是paramskey具有相同类型的两个参数,和一个QueryMap只是从一个字符串映射到任意IQuery(没有的类型是无论什么)。编译器在这里抱怨是因为它想要为 指定一个类型IQuery,但重点是IQuery映射中的每个都应该独立参数化。有没有办法在typescript中表达这一点?

此外,如果可能的话,我想QueryMapIQuery我遍历这棵树时获得有关上游s形状的信息/保证

3个回答

您可以做最简单的事情是:

export type QueryMap = {
  [k: string]: IQuery<any, any>
};

它不是完全类型安全的,但它与您要表示的内容相差不远。如果您不想丢失 type 值的类型信息QueryMap,请允许编译器推断更窄的类型并使用通用辅助函数来确保它是有效的QueryMap,如下所示:

const asQueryMap = <T extends QueryMap>(t: T) => t;

const queryMap = asQueryMap({
  foo: {
    request: "a",
    params(p: string, u?: number) { return {} },
    key(p: string, u?: number) { return "hey" },
    forceRequest: true
  }
});

尽管类型不是,但queryMap.foo.params仍然知道该值是接受 astring和 optional的方法numberQueryMap['foo']['params']

如果您指定不可分配给 a 的内容,QueryMap您将收到错误消息:

const bad = asQueryMap({
  foo: {
    request: "a",
    params(p: string, u?: number) { return {} },
    key(p: string, u?: number) { return "hey" },
    forceRequest: true
  },
  bar: {
    request: 123,
    params(p: number, u?: string) {return {}},
    key(p: number, u?: string) {return "nope"},
    forceRequest: false
  }
}); // error! bar.request is a number

这里显示了不完全类型安全的问题:

const notExactlySafe = asQueryMap({
  baz: {
    request: "a",
    params(p: number, u?: string) { return {} },
    key(p: string, u?: number) { return "hey" },
    forceRequest: true
  }
});

这是可以接受的,即使没有一致的合理值PU在这里有效(这就是您使用 时发生的情况any)。如果您需要更多地锁定它,您可以尝试让 TypeScript从值中推断出PU值的集合,或者如果不能,则警告您,但这不是直接的。

为了完整起见,这里就是我会做...使用条件类型来推断P,并U为您的每个元素QueryMap通过检查params方法,然后验证key方法进行匹配。

const asSaferQueryMap = <T extends QueryMap>(
  t: T & { [K in keyof T]:
    T[K]['params'] extends (p: infer P, u?: infer U) => any ? (
      T[K] extends IQuery<P, U> ? T[K] : IQuery<P, U>
    ) : never
  }
): T => t;

现在以下仍然有效:

const queryMap = asSaferQueryMap({
  foo: {
    request: "a",
    params(p: string, u?: number) { return {} },
    key(p: string, u?: number) { return "hey" },
    forceRequest: true
  }
});

虽然这现在将是一个错误:

const notExactlySafe = asSaferQueryMap({
  baz: {
    request: "a",
    params(p: number, u?: string) { return {} },
    key(p: string, u?: number) { return "hey" },
    forceRequest: true
  }
}); // error, string is not assignable to number

这会略微增加您的类型安全性,但会牺牲 类型中相当复杂的类型杂耍asSaferQueryMap(),所以我不知道这是否值得。 IQuery<any, any>对于大多数用途来说可能已经足够了。


好的,希望有帮助;祝你好运!

你可以使用IQuery<any, any>.

我不确定您在问题的第二部分希望什么。TypeScript 不会为您提供运行时类型信息。如果您只想在操作单个 时引用类型变量IQuery,则可以将 an 传递IQuery<any, any>给 a function myFunction<P, U>(iquery: IQuery<P, U>) { ... }

解决方案

为了清楚起见,我从您的类型中删除了不相关的信息。解决方案归结为基本上添加了 3 行代码。


type Check<T> = QueryMap<T extends QueryMap<infer U> ? U : never>

export type IQuery<P, U, TQueryMap extends Check<TQueryMap>> = {
    prop1: (param1: P, param2?: U) => number;
    prop2: (param1: P, param2?: U) => string;
    prop3?: TQueryMap
}

export type QueryMap<T> = {
  [K in keyof T]: T[K]
};

// type constructors
const asQueryMap = <T>(x: QueryMap<T>) => x
const asQuery = <P, U, V extends QueryMap<any>>(x: IQuery<P, U, V>) => x

注意事项

编译器正确推断出所有类型。

重要提示:如果(且仅当)您使用type constructors(见上文)构建您的结构时,您可以认为自己完全静态类型安全。

下面是测试用例:

测试 no compile errors


// Ok -- No compile-time error and correctly infered !

const queryMap = asQueryMap({
    a: asQuery({
        prop1: (param1: string, param2?: number) => 10,
        prop2: (param1: string, param2?: number) => "hello",
    }),

    b: asQuery({
        prop1: (param1: string, param2?: string) => 10,
        prop2: (param1: string, param2?: string) => "hello",
    }),

    c: asQuery({
        prop1: (param1: Array<number>, param2?: number) => 10,
        prop2: (param1: Array<number>, param2?: number) => "hello",
    })
})


const query = asQuery({
    prop1: (param1: string, param2?: number) => 10,
    prop2: (param1: string, param2?: number) => "hello",
    prop3: queryMap    
})

测试 Compile-time errors

您可以在下面看到一些被捕获的编译时错误。


// Ok --> Compile Error: 'prop2' signature is wrong

const queryMap2 = asQueryMap({
    a: asQuery({
        prop1: (param1: Array<string>, param2?: number) => 10,
        prop2: (param1: Array<number>, param2?: number) => "hello",
    })
})


// Ok --> Compile Error: 'prop3' is not of type QueryMap<any>

const query2 = asQuery({
    prop1: (param1: string, param2?: number) => 10,
    prop2: (param1: string, param2?: number) => "hello",
    prop3: 10 // <---- Error !
})

谢谢干杯