如何在 class 或 TypeScript 中的对象中要求特定数据类型?
How to require a specific data type in a class or object in TypeScript?
我知道有Record和Pick,但是不知道怎么用好或者不完全明白。好吧,我想实现的是,在一个class或对象中至少它有一个特定的数据类型,例如,class或对象可以包含字符串、布尔值、数组等,但是它必须至少具有函数类型的属性或字段。
interface TypeAtLeastRequired {
[index: string]: Function;
}
class Foo implements TypeAtLeastRequired { //<-- Error: This class must have at least one function
msg: string = 'Hello!';
}
// or something like that:
type IsRequired<C> = {
[K in keyof C]: C[K] ThatAtLeastBe Function;
};
我希望文字对象也一样 ({})
是否可以使用 TypeScript 做类似的事情?
希望您能帮助我理解或找到可能的解决方案:)
TypeScript 中没有特定类型与 Java 具有至少一个函数值 属性.
的脚本对象集完全对应
Index signatures 不起作用,因为它们根本不需要任何属性(空对象 {}
可分配给 {[k: string]: Function}
)并且因为它们需要任何 属性 is present 必须是特定类型(因此对象 {a: ()=>{}, b: 0}
not 可分配给 {[k: string]: Function}
因为 b
属性 不是函数)。所以我们应该放弃索引签名。
相反,我们可以将您正在做的事情表达为 generic constraint, and a fundamentally recursive constraint (sometimes known as F-bounded polymorphism)。因此,不是 TypeAtLeastRequired
是特定类型,而是像 T extends TypeAtLeastRequired<T>
这样的操作。第一个 T
代表你正在检查的类型, TypeAtLeastRequired
是你应用到 T
并得到一些东西的类型函数。如果你正确地构建了那个类型的函数,当且仅当 T extends typeAtLeastRequired<T>
.
时,你可以使 T
对应一个至少有一个函数值 属性 的对象
这是一种可能的实现方式:
type TypeAtLeastRequired<T> = (
{ [K in keyof T]-?: T[K] extends Function ? unknown : never }[keyof T]
) extends never ? { "please add at least one function prop": Function } : T
如果 T
没有函数值属性,则 mapped type {[K in keyof T]-?: T[K] extends Function ? unknown : never}[keyof T]
will have all its properties be the never
type. Otherwise, at least one property will not be the unknown
type 而不是 never
。
由其所有属性的indexing into that mapped type with keyof T
, we get the union;如果 T
的属性中的 none 是函数,那么联合将为 never
(never
| never
也是 never
)。如果至少有一个 属性 是函数,则并集将是 unknown
(never
| unknown
是 unknown
)。
然后我们使用 conditional type 来对照 never
检查这个新的 unknown
或 never
值。如果我们没有函数属性,这就是 never
,所以我们最终得到条件的真正分支:{"please add...": Function}
。如果我们至少有一个函数属性,那就是 unknown
,所以我们最终得到条件的假分支:T
.
所以在T
没有函数值属性的情况下,TypeAtLeastRequired<T>
将是{"please...": Function}
,而T extends {"please...": Function}
则不是。但如果它至少有一个函数值 属性,TypeAtLeastRequired<T>
将是 T
,并且 T extends T
为真。
所以你可以看到T extends TypeAtLeastRequired<T>
如何表达想要的约束。
所以现在,让我们使用它。如果你想在 implements
子句中使用它,你必须提到实现 class 名称两次。它是重复的,但并不可怕(Java 一直这样做):
class Foo implements TypeAtLeastRequired<Foo> { // error
msg: string = 'Hello!';
}
class Bar implements TypeAtLeastRequired<Bar> {
msg: string = 'Hello!';
okay() { }
}
如果你想用对象文字来做,如果你必须给类型一个名字只是为了检查它,你会很生气。相反,您可以使用通用辅助函数来 推断 类型:
const typeAtLeastRequired = <T,>(t: TypeAtLeastRequired<T>) => t;
而不是写 const v: TypeAtLeastRequired = {...}
,你写 const v = typeAtLeastRequired({...})
。像这样:
const okay = typeAtLeastRequired({
a: 0,
b: () => 2
})
const notOkay = typeAtLeastRequired({
a: 0, // error!
b: 2
})
我知道有Record和Pick,但是不知道怎么用好或者不完全明白。好吧,我想实现的是,在一个class或对象中至少它有一个特定的数据类型,例如,class或对象可以包含字符串、布尔值、数组等,但是它必须至少具有函数类型的属性或字段。
interface TypeAtLeastRequired {
[index: string]: Function;
}
class Foo implements TypeAtLeastRequired { //<-- Error: This class must have at least one function
msg: string = 'Hello!';
}
// or something like that:
type IsRequired<C> = {
[K in keyof C]: C[K] ThatAtLeastBe Function;
};
我希望文字对象也一样 ({})
是否可以使用 TypeScript 做类似的事情?
希望您能帮助我理解或找到可能的解决方案:)
TypeScript 中没有特定类型与 Java 具有至少一个函数值 属性.
的脚本对象集完全对应Index signatures 不起作用,因为它们根本不需要任何属性(空对象 {}
可分配给 {[k: string]: Function}
)并且因为它们需要任何 属性 is present 必须是特定类型(因此对象 {a: ()=>{}, b: 0}
not 可分配给 {[k: string]: Function}
因为 b
属性 不是函数)。所以我们应该放弃索引签名。
相反,我们可以将您正在做的事情表达为 generic constraint, and a fundamentally recursive constraint (sometimes known as F-bounded polymorphism)。因此,不是 TypeAtLeastRequired
是特定类型,而是像 T extends TypeAtLeastRequired<T>
这样的操作。第一个 T
代表你正在检查的类型, TypeAtLeastRequired
是你应用到 T
并得到一些东西的类型函数。如果你正确地构建了那个类型的函数,当且仅当 T extends typeAtLeastRequired<T>
.
T
对应一个至少有一个函数值 属性 的对象
这是一种可能的实现方式:
type TypeAtLeastRequired<T> = (
{ [K in keyof T]-?: T[K] extends Function ? unknown : never }[keyof T]
) extends never ? { "please add at least one function prop": Function } : T
如果 T
没有函数值属性,则 mapped type {[K in keyof T]-?: T[K] extends Function ? unknown : never}[keyof T]
will have all its properties be the never
type. Otherwise, at least one property will not be the unknown
type 而不是 never
。
由其所有属性的indexing into that mapped type with keyof T
, we get the union;如果 T
的属性中的 none 是函数,那么联合将为 never
(never
| never
也是 never
)。如果至少有一个 属性 是函数,则并集将是 unknown
(never
| unknown
是 unknown
)。
然后我们使用 conditional type 来对照 never
检查这个新的 unknown
或 never
值。如果我们没有函数属性,这就是 never
,所以我们最终得到条件的真正分支:{"please add...": Function}
。如果我们至少有一个函数属性,那就是 unknown
,所以我们最终得到条件的假分支:T
.
所以在T
没有函数值属性的情况下,TypeAtLeastRequired<T>
将是{"please...": Function}
,而T extends {"please...": Function}
则不是。但如果它至少有一个函数值 属性,TypeAtLeastRequired<T>
将是 T
,并且 T extends T
为真。
所以你可以看到T extends TypeAtLeastRequired<T>
如何表达想要的约束。
所以现在,让我们使用它。如果你想在 implements
子句中使用它,你必须提到实现 class 名称两次。它是重复的,但并不可怕(Java 一直这样做):
class Foo implements TypeAtLeastRequired<Foo> { // error
msg: string = 'Hello!';
}
class Bar implements TypeAtLeastRequired<Bar> {
msg: string = 'Hello!';
okay() { }
}
如果你想用对象文字来做,如果你必须给类型一个名字只是为了检查它,你会很生气。相反,您可以使用通用辅助函数来 推断 类型:
const typeAtLeastRequired = <T,>(t: TypeAtLeastRequired<T>) => t;
而不是写 const v: TypeAtLeastRequired = {...}
,你写 const v = typeAtLeastRequired({...})
。像这样:
const okay = typeAtLeastRequired({
a: 0,
b: () => 2
})
const notOkay = typeAtLeastRequired({
a: 0, // error!
b: 2
})