我想创建一个函数 X,它接受一组受约束的函数 Y,使得它们的参数必须是
object
的子类型。我希望从 Y 的参数推断出 Y 的返回类型。例如,如果用户传递 [(x: {a:1}) => void]
那么返回类型应该是字面意思。
当我尝试在 TypeScript 中创建 X 时,我总是遇到逆变问题。如果我说我希望 X 只接受
(input:object) => void
子类型的函数,那么 object
就被视为逆变约束,意味着只允许 object
的超类型。我想要相反的子类型被允许。
如何实现这一目标?这是我最近对这个问题所做的尝试:
type InputFunction<$I> = <$Input extends $I>(input: $Input) => void
declare const createObjectAccepingFunctions: <const $Fs extends InputFunction<object>[]>(fs: $Fs) => { items: $Fs }
const result = createObjectAccepingFunctions([
(_: { a: 1 }) => {}, // Should pass
(_: string) => {}, // Should fail
])
result.items
您不能将函数的参数必须是
object
的子类型的限制表示为函数类型上的直接 通用约束。正如您所指出的,函数的参数类型是逆变的(请参阅 TypeScript 中的方差、协方差、逆变、双方差和不变性之间的差异),因此像 F extends ((x: object)=>void)[]
这样的上界约束最终看起来像下界 约束关于
x
参数。由于 TypeScript 没有对下限约束的内置支持(如microsoft/TypeScript#14520 中的要求),因此您也不能编写
F super ((x: object)=>void)[]
。 这样的约束本身并不完全有效,因为返回类型将是逆变的。 因此,我们必须放弃简单的约束,并进行重构。有不同的可能方法。
元组进行约束,然后使用映射数组类型来表示实际的函数数组。像这样:
declare const createObjectAccepingFunctions: <const A extends object[]>(
fs: { [I in keyof A]: (input: A[I]) => void }
) => { items: typeof fs }
因此,当您调用 createObjectAccepingFunctions([(a: A1)=>{}, (a: A2)=>{}, (a: A3) =>{}])
时,TypeScript 会将
A
推断为
[A1, A2, A3]
并将其与
object[]
进行比较。如果成功,那么您的调用成功,否则失败,您将收到错误消息:
const result = createObjectAccepingFunctions([
(_: { a: 1 }) => { },
(_: { a: 2 }) => { },
]) // okay
result.items;
// ^? [(input: { a: 1; }) => void, (input: { a: 2; }) => void]
createObjectAccepingFunctions([
(_: { a: 1 }) => { },
(_: string) => { },
]); // error!
不幸的是,在错误情况下,A
的推理失败并回退到约束
object[]
,因此错误消息只是抱怨一切。
object
的函数,在这种情况下,我们保留该元素,要么它不是一个,在这种情况下,我们将其替换为其他东西,希望能为用户提供一些线索,告诉用户问题是什么:
declare const createObjectAccepingFunctions: <const F extends ((x: any) => void)[]>(
fs: { [I in keyof F]: F[I] extends (x: infer A) => void ?
[A] extends [object] ? F[I] : never :
never }
) => { items: typeof fs }
因此,对于索引为 F
的元素处
I
的每个成员,如果inferred 参数
A
是
object
的子类型,那么我们将其保留为
F[I]
。否则我们将其替换为
never
,它将无法扩展。 这会给你这样的行为:
const result = createObjectAccepingFunctions([
(_: { a: 1 }) => { },
(_: { a: 2 }) => { },
]); // okay
result.items;
// ^? [(input: { a: 1; }) => void, (input: { a: 2; }) => void]
createObjectAccepingFunctions([
(_: { a: 1 }) => { },
(_: string) => { }, // error!
//~~~~~~~~~~~~~~~~
// Type '(_: string) => void' is not assignable to type 'never'
]);
这也不是很有帮助,但至少你的错误仅限于糟糕的论点。不幸的是,TypeScript 缺少 microsoft/TypeScript#23689 中要求的“无效类型”,允许您自定义错误消息。理想情况下,您的消息应该是“string
不可分配给
object
”之类的内容。 我们能想到的最接近的方法是定义一个
Invalid<T>
类型,我们希望不会意外地为它分配任何内容,然后我们可以传入
T
一些看起来像自定义错误消息的东西(如果你眯着眼睛看的话)。 也许是这样的:
interface Invalid<T> {
msg: T;
}
declare const createObjectAccepingFunctions: <const F extends ((x: any) => void)[]>(
fs: { [I in keyof F]: F[I] extends (x: infer A) => void ?
[A] extends [object] ? F[I] : Invalid<[A, "is not assignable to object"]> :
never }
) => { items: typeof fs }
现在我们得到了相同的成功行为,但失败看起来像
createObjectAccepingFunctions([
(_: { a: 1 }) => { },
(_: string) => { }, // error!
//~~~~~~~~~~~~~~~~
// Type '(_: string) => void' is not assignable to type
// 'Invalid<[string, "is not assignable to object"]>'.
]);
这已经是我能想到的最接近有意义的错误了。