从功能上来说,我想提供2个接口来访问我的数据库:
dao
可能由管理员或普通用户使用,因此我们需要提供 isAdmin:boolean
作为每个函数的第一个参数(例如:updateUser(isAdmin: boolean, returnUser)
)daoAsAdmin
提供了一个接口,可以在没有isAdmin
参数的情况下调用方法(例如:updateUser(returnUser)
)这是一个代码示例:
type User = { name: string }
type DaoAsAdmin = {
updateUser<ReturnUser extends boolean>(
returnUser: ReturnUser
): ReturnUser extends true ? User : string
}
type Dao = {
// injects `isAdmin` as first param of all dao methods
[K in keyof DaoAsAdmin]: (isAdmin: boolean, ...params: Parameters<DaoAsAdmin[K]>) => ReturnType<DaoAsAdmin[K]>
}
// Here is the real code
const dao: Dao = {
updateUser(isAdmin, returnUser) {
throw 'not implemented'
}
}
// Here I use this proxy trick to inject isAdmin = true
// as the first param of each dao methods
const daoAsAdmin = new Proxy(dao, {
get(target, prop, receiver) {
return function (...params) {
const NEW_PARAMS = [true, ...params]
return target[prop](NEW_PARAMS)
}
},
}) as DaoAsAdmin
// So now, I can call updateUser like so
const userAsAdmin = daoAsAdmin.updateUser(true) // is type User as expected
const userStringAsAdmin = daoAsAdmin.updateUser(false) // is type string as expected
// unfortunately this doesn't work with dao
const user = dao.updateUser(false, true) // is type string | User when User was expected
const userAsStr = dao.updateUser(false, false) // is type string | User when string was expected
所以我尝试了不同的方法,但无法让
dao
函数返回正确的类型。看起来它需要 Parameters
和 ReturnType
的混合,但是没有任何关于使用 ReturnType
和提供函数将使用的参数类型的信息。
我应该在
Dao
类型定义中更改哪些内容才能满足我的期望?
现实生活中的例子要复杂得多,不幸的是我必须单独声明类型和常量。如果您需要更多详细信息,请告诉我。
generic 函数类型。 TypeScript 缺乏 microsoft/TypeScript#1213 中要求的更高种类的类型,即使它有它们,你也不清楚如何编写你正在寻找的类型转换。
如果您尝试在泛型函数上使用条件类型(例如Parameters<T>
ReturnType<T>
实用程序类型),最终会删除泛型。 因此,没有好的方法来编写您正在执行的类型操作以保留泛型。
支持用于在值级别操作通用函数类型,如microsoft/TypeScript#30125中实现。 因此,给定泛型函数类型的值 gf
,您可以编写另一个函数
hof()
,以便
hof(gf)
返回相关的泛型函数类型。 对于您的示例代码,它看起来像:
function injectIsAdmin<A extends any[], R>(
f: (...a: A) => R
): (isAdmin: boolean, ...a: A) => R {
throw 0; // you can implement this if you want but it's not needed
}
您可以通过示例了解它是如何工作的:
const g = <T extends string, U extends number>(t: T, u: U) => [t, u] as const;
// const g: <T extends string, U extends number>(
// t: T, u: U
// ) => readonly [T, U];
const gi = injectIsAdmin(g);
// const gi: <T extends string, U extends number>(
// isAdmin: boolean, t: T, u: U
// ) => readonly [T, U];
这很棒,但它无法按照您想要的方式扩展。 您不能对映射类型执行此操作:
function mapInjectAsAdmin<A extends Record<keyof R, any[]>, R extends Record<keyof A, any>>(
f: { [K in keyof A]: (...args: A[K]) => R[K] } & { [K in keyof R]: (...args: A[K]) => R[K] }
): { [K in keyof A]: (isAdmin: boolean, ...args: A[K]) => R[K] } {
throw 0;
}
const badGi = mapInjectAsAdmin({ oops: g });
// const badGi: {
// oops: (isAdmin: boolean, t: string, u: number) => readonly [string, number]; 👎
// }
因此,您必须通过手动遍历所有键来从 Dao
定义
DaoAsAdmin
。 并且您需要使用函数来执行此操作,因此如果您只想计算类型,则必须欺骗编译器,使其认为您实际上正在运行未运行的代码。 像这样混乱的事情:
function daoTypeBuilder() {
if (true as false) throw 0; // exit fn without the compiler realizing
function injectIsAdmin<A extends any[], R>(
f: (...a: A) => R
): (isAdmin: boolean, ...a: A) => R {
throw 0;
}
const daoAsAdmin: DaoAsAdmin = null!
const dao = {
// manually write this out for each of these
updateUser: injectIsAdmin(daoAsAdmin.updateUser)
} satisfies Record<keyof DaoAsAdmin, any>
return dao;
}
type Dao = ReturnType<typeof daoTypeBuilder>
/* type Dao = {
updateUser: <ReturnUser extends boolean>(
isAdmin: boolean, returnUser: ReturnUser
) => ReturnUser extends true ? User : string;
} */
这正是你想要的类型,但我不知道是否值得。 您基本上可以欺骗编译器计算您关心的类型,但这需要大量粗略的手动代码。