Javascript 任意泛型的Typescript映射

Javascript 任意泛型的Typescript映射,javascript,reactjs,typescript,generics,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: st

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

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
};
导出类型IQuery={
请求:字符串;
参数:(道具:P,团队?:U)=>对象;
键:(道具:P,上游?:U)=>字符串;
forceRequest:布尔;
取决于:查询映射
}
导出类型查询映射={
[k:字符串]:iquiry
};
我试图表达的约束条件是,
params
key
的两个参数具有相同的类型,而QueryMap只是从字符串到任意
IQuery
的映射(不管类型是什么)。编译器在这里抱怨,因为它希望为
IQuery
指定一个类型,但问题是映射中的每个
IQuery
都应该独立参数化。有没有办法用打字脚本来表达这一点


此外,如果可能的话,我希望在遍历此树时获得上游
QueryMap
中存在的
IQuery
形状的信息/保证。

您可以使用
IQuery

我不确定你在问题的第二部分希望得到什么。TypeScript不提供运行时类型信息。如果您只想在操作单个
IQuery
时引用类型变量,可以将
IQuery
传递给
函数myFunction(IQuery:IQuery){…}
您可以做的最简单的事情是:

export type QueryMap = {
  [k: string]: IQuery<any, any>
};
queryMap.foo.params
仍然是一个接受
字符串和可选
数字的方法,即使类型
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 = 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

不完全类型安全问题如下所示:

const notExactlySafe = asQueryMap({
  baz: {
    request: "a",
    params(p: number, u?: string) { return {} },
    key(p: string, u?: number) { return "hey" },
    forceRequest: true
  }
});
这是可以接受的,即使这里没有一致的合理值
p
U
(这是使用
any
时发生的情况)。如果需要进一步锁定此项,可以尝试让TypeScript从值中推断
P
U
值集,或者在不能的情况下向您发出警告,但这不是直截了当的

为了完整起见,我会这样做。。。用于通过检查
params
方法推断
QueryMap
中每个元素的
P
U
,然后验证
方法是否匹配

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 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 = 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
对于大多数用途来说可能已经足够好了


好吧,希望这会有帮助;祝你好运

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


类型检查=查询映射
导出类型IQuery={
prop1:(参数1:P,参数2?:U)=>数字;
prop2:(param1:P,param2?:U)=>字符串;
prop3?:TQueryMap
}
导出类型查询映射={
[K in keyof T]:T[K]
};
//类型构造函数
常量asQueryMap=(x:QueryMap)=>x
常量asQuery=(x:IQuery)=>x
考虑 所有类型都由编译器正确推断

重要:如果(仅当)使用<代码>类型构造函数< /代码>(见上文)来构造您的结构,则可以认为自己完全静态类型安全。

以下是测试用例:

测试
无编译错误
多谢各位
干杯

这很有帮助。关于条件类型和使用推断关键字的事情或多或少就是我想要的。我应该说,这些地图将是预定义的(它们是整个应用程序中使用的查询),所以我可以让它们自己输入;我只是想让linter/编译器在我进行错误查询时大叫。我还想保证某些函数返回的形状,即
getResponse(QueryMap)
的返回形状等于map和上游map中的所有键。太好了,谢谢你!

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

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