我希望能够“配置”工厂以创建类型保护功能。这样我就可以传入函数(按名称键入),然后为它们创建类型保护程序。 (Playground link)
type Success<T> = [true, T]
type Failure = [false, any]
type Result<T> = Success<T> | Failure
type Validator<T> = (x: any) => Result<T>
type Validators = {
[key: string]: <T>(x: any) => Result<T>
}
type Value<T> = T extends Validator<infer V> ? V : never
const configure = <V extends Validators>(validators: V) => {
return <K extends keyof V>(key: K) => {
return <T extends Value<V[K]>>(x: any): x is T => {
const validator = validators[key]
const [valid] = validator(x)
return valid
}
}
}
那可以用作...
const factory = configure({
number: (x: any) => typeof x === 'number' ? [true, x] : [false, 'Invalid!']
// ^ Error here!
})
const isNumber = factory('number')
const val = 42 as unknown
if (isNumber(val)) {
val
}
但这会导致此编译错误:
(property) number: (x: any) => [true, number] | [false, string]
Type '(x: any) => [true, number] | [false, string]' is not assignable to type '<T>(x: any) => Result<T>'.
Type '[true, number] | [false, string]' is not assignable to type 'Result<T>'.
Type '[true, number]' is not assignable to type 'Result<T>'.
Type '[true, number]' is not assignable to type 'Success<T>'.
Type 'number' is not assignable to type 'T'.
'number' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'.(2322)
input.ts(6, 3): The expected type comes from this index signature.
为什么不能动态识别类型保护的值?
答案 0 :(得分:2)
您有一些通用的函数类型。这是主要问题:
type Validators = {
[key: string]: <T>(x: any) => Result<T>
}
该对象的每个属性值的类型均为<T>(x: any)=>Result<T>
,这是一个通用函数,声称可以为调用者指定的任何Result<T>
生成T
< / em>。很难想象这样的函数:如果您有一个名为f
的值和一个未知值名为x
的值,则可以将其称为f<string>(x)
来查看x
是否是一个string
,然后将其称为f<number>(x)
来查看x
是否是number
,这很令人惊奇,因为f
只有一个运行时实现。
因此,编译器正确地抱怨您的Validator<number>
不是<T>(x: any)=>Result<T>
。真的,什么都不会。
但是,那不是您想要Validators
的属性。您真的想说每个属性都是一些特定的 Validator<T>
,但是您还不知道哪个。从概念上讲,这称为existential type,但是TypeScript并不容易支持它们。您可以尝试将其表示为映射的对象类型,但是现在我们将举起手来将<T>(x: any)=>Result<T>
更改为Validator<any>
。至少有可能实现。
现在我们有了
const configure = <V extends {[k: string]: Validator<any>>(validators: V) => {...}
这很好。我通常更喜欢将索引签名替换为如下所示的自引用映射类型:
const configure = <V extends Record<keyof V, Validator<any>>>(validators: V) => {...}
因为它不需要validators
具有索引签名,如果它是对象文字或类型别名类型,则可以使用,但fail if it of an interface type可以。
继续前进:在您的实现中,您拥有类型
<T extends Value<V[K]>>(x: any): x is T
又以无法实现的方式通用。如果Value<V[K]>
为number
,则此函数为<T extends number>(x: any): x is T
。使用这种类型的名为g
的函数和名为x
的值,您可以调用g<123>(x)
来确定x
是否为123
,也可以致电g<456>(x)
来确定x
是否为456
。无法实现。让我们将其更改为
(x: any): x is Value<V[K]>
这是您真正想要的:如果Value<V[K]>
是number
,则会得到(x: any): x is number
,这是您可以实际实现的非泛型类型保护函数。
因此,我们将所有内容放在一起进行尝试。编译器错误消失,并且:
const factory = configure({
number: (x: any) => typeof x === 'number' ? [true, x] : [false, 'Invalid!'], // okay
})
const isNumber = factory('number');
//const isNumber: (x: any) => x is string | number ?!
哦,亲爱的。出于某种原因,编译器已推断Value<(x: any) => [true, number] | [false, string]>
为string | number
。这种推论并非完全错误,但并不如应有的精确。此处的解决方案是放弃编译器的神奇条件推断Validator<infer V>
,卷起袖子,并尝试从V
手动计算T extends Validator<any>
:
type Value<T extends Validator<any>> = Extract<ReturnType<T>, [true, any]>[1]
那是说:给定T
的{{1}},首先得到它的返回类型。看起来像Validator<any>
。由此,我们应该只看匹配[true, XXX] | [false, YYY]
...的那个联合的那一部分,所以只看[true, any]
。最后,获取该元组类型的第二个元素(使用[true, XXX]
索引)以获取[1]
。
现在,最终,发生这种情况:
XXX
万岁!好的,希望能有所帮助;祝你好运!