获取使用某些参数类型调用的通用函数的通用 ReturnType

问题描述 投票:0回答:1

从功能上来说,我想提供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
类型定义中更改哪些内容才能满足我的期望?

现实生活中的例子要复杂得多,不幸的是我必须单独声明类型和常量。如果您需要更多详细信息,请告诉我。

打字稿游乐场

typescript types parameters return-type
1个回答
1
投票
不幸的是,TypeScript 不允许在类型级别任意操作

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; } */
这正是你想要的类型,但我不知道是否值得。  您基本上可以欺骗编译器计算您关心的类型,但这需要大量粗略的手动代码。


所以你就可以了;如果没有一些令人不快的技巧,这是不可能做到的。

Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.