如何在 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 是函数,那么联合将为 nevernever | never 也是 never)。如果至少有一个 属性 是函数,则并集将是 unknownnever | unknownunknown)。

然后我们使用 conditional type 来对照 never 检查这个新的 unknownnever 值。如果我们没有函数属性,这就是 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
})

Playground link to code