编辑:今天玩了一下之后我完全改变了问题: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 类型,无论是未定义的还是字符串。然而,这是“任何”类型。 在这种情况下有什么可以做的,还是我只是运气不好?
如果您有一个函数参数的元组类型
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]
。