任意泛型的打字稿图

时间:2018-07-26 20:54:10

标签: javascript reactjs typescript generics

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

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只是从字符串到任意{{1 }}(类型无关紧要)。编译器在这里抱怨是因为它希望为IQuery指定一个类型,但是重点是映射中的每个IQuery应该独立地参数化。有什么办法可以用打字稿表达出来?

此外,如果可能的话,我想获得有关IQuery中存在的上游QueryMap的形状的信息/保证,当我遍历这棵树时。

3 个答案:

答案 0 :(得分:2)

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

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

它不是完全类型安全的,但与您要表示的内容相差不远。如果您不想丢失类型为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不是,值string仍然是接受number和可选QueryMap['foo']['params']的方法。

如果您指定了不可分配给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值的集合,或者如果不能,则警告您,但这不是明智的选择。

为了完整起见,这是我的操作方法...使用conditional types通过检查{{来推断P中每个元素的UQueryMap 1}}方法,然后验证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()可能足以满足大多数目的。


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

答案 1 :(得分:0)

您可以使用IQuery<any, any>

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

答案 2 :(得分:0)

解决方案

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


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 !
})

谢谢 干杯