TypeScript 类型 - 从函数调用访问函数的参数元组类型(由另一种类型生成的 Fn 类型)

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

编辑:今天玩了一下之后我完全改变了问题:Playground

我从最后开始,为了简单起见,我希望获得用户提供的正确的第一个参数:

const functionWithoutRestrictedParams = <Args extends any[]>(...args: Args): Args[0] => {
    return '' as any
}

const stringResult = functionWithoutRestrictedParams('first', 'second')

const undefinedResult = functionWithoutRestrictedParams(undefined, 'second')

这是一个函数,它接受任意数量的参数,并具有返回类型作为用户提供的第一个参数的类型。

现在让我们开始输入函数参数: 首先只是一个简单的类型,它接受函数参数的类型并使每个参数都是可选的:

export type FnWithOptions<A extends any[]> = {[I in keyof A]: A[I] | undefined}

type BasicFn<Params extends any[]>
    = (...args: FnWithOptions<Params>) => void


const fn:BasicFn<[first: string, second: number]> = (first, second) => {}

fn('test', 1)
fn(undefined, 1)
//@ts-expect-error
fn(1,1)

这是为了显示Api使用什么样的类型,每个参数都有可能是可选的。

现在我认为最接近工作的尝试,但遗憾的是结果类型是any,因此类型信息丢失了。

type MockTransformer<Params, Args> = Params

type Attempt<Params extends any[]>
    = <Args extends any[]>(...args:MockTransformer<FnWithOptions<Params>, Args>) => Args[0]

const attempt:Attempt<[first: string, second: number]> = (first, second) => {}

const expectedString = attempt("str", 1)
const expectedUndefined = attempt(undefined, 1)

在这里,我尝试结合前面的两个示例,首先定义定义函数时始终提供的必需参数。然后定义Function类型。我在这里的推理是 MockTransformer 类型接受 Params 和 Args,返回 Params,以便这是用户可以提供的数据类型。然后我希望调用函数的实际参数绑定到 Args 并保留类型信息,并且根据提供的值,我将获得正确的 Return 类型,无论是未定义的还是字符串。然而,这是“任何”类型。 在这种情况下有什么可以做的,还是我只是运气不好?

typescript types
1个回答
0
投票

如果您有一个函数参数的元组类型

P
,并且您想要创建一个可以跟踪传入参数类型的函数,即使这些类型比
P
窄,那么您可以使函数通用

<A extends P>(...args: A) => void

现在,如果您调用该函数,TypeScript 将根据实际参数的类型推断

A
。 这些参数的类型是 constrained
P
,因此如果
A
[string]
那么你不能使用
number
调用该函数。

如果函数应该返回第一个参数的类型,那么您可以返回 索引访问类型

A[0]
:

<A extends P>(...args: A) => A[0]

在您的具体情况下,您实际上不想接受

P
,而是
P
的修改版本,其中每个元素可能是
undefined
。因此,您需要
A extends P
,而不是
A extends UndefinableElements<P>
,其中
UndefinableProps
映射数组/元组类型,定义为:

type UndefinableElements<T extends any[]> =
    { [I in keyof T]: T[I] | undefined }

因此,如果

P
[string]
,那么
UndefinableElements<P>
就是
[string | undefined]
。 所以你的函数类型看起来像:

type Attempt<P extends any[]> =
    <A extends UndefinableElements<P>>(...args: A) => A[0]

再次,这里发生的事情是,

Attempt<P>
会将元组类型
P
转换为所有元素都可以接受
undefined
的版本,然后它将成为一个泛型函数,其中参数列表是某种泛型类型
A 
限制为
P
的修改版本,并且返回第一个参数
A[0]
的类型。 我们来测试一下:

const attempt: Attempt<[first: string, second: number]> =
    (first, second) => first

const expectedString = attempt("str", 1);
//    ^? const expectedString: "str"

const expectedUndefined = attempt(undefined, 1);
//    ^? const expectedUndefined: undefined

attempt(0, 1); // error!
//      ~
// Argument of type 'number' is not assignable to parameter of type 'string'.

看起来不错。第一个调用将

A
推断为
["str", 1]
,因此
A[0]
"str"
,而第二次调用将
A
推断为
[undefined, 1]
,因此
A[0]
undefined
。 第三次调用会产生错误,因为类型
[0, 1]
TypeScript wants 推断
A
UndefinableElements<[first: string, second: number]>
不匹配,即
[first: string | undefined, second: number | undefined]

Playground 代码链接

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