将预定义的参数形状映射到已知返回类型的打字稿泛型映射



我正在尝试描述采用预定义形式的输入对象并返回众所周知的返回对象类型的函数类型。

db.queryObject({kind: "user", name: "Bobby"}) => User(...)
db.queryObject({kind: "article", author: ["Bobby"], titleStartsWith: "Introduction to"}) => Article(...)

我同意最好为每种类型的对象提供专用方法,但是如何解决当前设置中的问题?

我遵循了这种方法:

我声明了查询 QUser、QArticle、.. 和一组返回类型 RUser、RArticle、.. 的预定义形状,并将它们放入这样的容器中

export type Queries = {
user: QUser
article: QArticle
}
export type Returns = {
user: RUser
article: RArticle
}

我试过了

let query: (q: Queries[keyof Queries]) => Returns[keyof Returns] = ....

没用,因为查询期望QUser | QArticle并返回RUser| RUser,我想有严格的QUser=>RUser

然后我尝试了类似的东西

type KQ = keyof Query;
let func: <Q extends Queries, K extends KQ>(a: Q[K]) => B[K] = p => {
if (p === 1) {
return 11;
}
};

问题是返回值是RUser & RArticle

如何解决这个问题?

这是更简单的游戏版本

type A = {
a: 1;
b: 2;
};
type B = {
a: 11;
b: 22;
};
type KA = keyof A;
// expect no error
let func: <P extends A, V extends KA>(a: P[V]) => B[V] | null = p => {
if (p === 1) {
return 11;
}
return null
};

下面所有示例的通用代码。

interface QUser { quser: null }
interface QArticle { qarticle: null }
interface RUser { ruser: null }
interface RArticle { rarticle: null }
const qUser: QUser = 0 as any
const qArticle: QArticle = 0 as any

执行此操作的最直接方法是使用函数重载。基本上,这个想法是简单地列出所有内容:

  • 如果参数QUser,则结果为RUser;
  • 如果参数QArticle,则结果为QArticle
function query (q: QUser): RUser
function query (q: QArticle): RArticle
function query (q: any): any {
// implementation
}

当 TypeScript 查看具有重载的函数的函数类型时,它会忽略您在实际函数定义中给出的类型(在我的示例中有两个anys 的行(。它仅从上到下查看重载,并使用先到比赛获胜的策略搜索匹配项。

请注意,您的函数query仍然需要在类型之间执行运行时差异才能返回正确的结果。重载将仅提供静态类型信息。因此,最好为每个查询定义类型保护。

function isQUser (x: unknown): x is QUser { return /* test for QUser */ }
function isQArticle (x: unknown): x is QArticle { return /* test for QArticle */ }

现在,您可以在函数中使用它们。

function query (q: QUser): RUser
function query (q: QArticle): RArticle
function query (q: any): any {
if (isQUser(q)) {
return /* ... */
} else if (isQArticle(q)) {
return /* ... */
} else {
console.log(q)
throw new Error(`Unhandeled type of query.`)
}
}

重载的唯一问题是,每次添加新的查询/结果接口对时,都需要记住添加重载。请注意,这应该不是问题,因为添加新对也意味着您需要更改实现本身。

相关内容

最新更新