将泛型类型 <T> 限制为特定类型

时间:2021-05-14 17:17:26

标签: typescript typescript-generics

在代码片段中,函数 doSomething 应该只接受 Base 类型的对象,而不接受其他匹配类型结构的类型,例如 instanceOf 约束。

interface IBase {
    prop1: string;
}

abstract class Base implements IBase {
    constructor() { }
    prop1: string = '';
}
class Derived1 extends Base { }
class Derived2 extends Base { }

class AnotherBase {
    constructor() { }
    prop1: string = '';
}

// T extends Base
function doSomething<T extends Base>(obj: T) {
    // do something
}

//cases should work
doSomething(new Derived1());
doSomething(new Derived2());

// cases should NOT work
doSomething({ prop1: 'test' }); // works
doSomething(new AnotherBase()); // works

我们怎样才能让函数严格只接受 Base 类型的对象? 我们可以理解当前按照 type-compatibility 规则工作。但是,是否有一种方法可以指定通用约束以匹配精确类型?

更新:

从这个和类似问题的答案来看,实现这个编译时似乎是不可能的。我决定包含 instanceOf 检查以在运行时实现预期行为。

// T extends Base
function doSomething<T extends Base>(obj: T) {
    if(!( obj instanceof Base)){
        console.log('not a Base type');
        throw new Error('An instance of Base is expected.');
    }
    // do something
}

//cases should work
console.log('Derived1');
doSomething(new Derived1());

console.log('Derived2');
doSomething(new Derived2());

// cases should NOT work
console.log('{..}');
doSomething({ prop1: 'test' }); // doesn't work

console.log('AnotherBase');
doSomething(new AnotherBase()); // doesn't work

1 个答案:

答案 0 :(得分:2)

简短回答:不。


打字稿是结构性的,而不是名义上的。这使得如果两种类型具有相同的接口,那么它们就是相同的类型。这甚至包括类实例与对象字面量。

我们可以用类似的东西来测试:

type Exact<A, B> = A extends B ? B extends A ? true : false : false

这个类型别名需要两个参数。如果 A 扩展了 B,反之亦然,那么我们知道 typescript 认为它们是相同的。

type IBase_Base = Exact<IBase, Base> // true
type Base_Derived = Exact<Base, Derived> // true
type Derived_ObjLiertal = Exact<Base, { prop1: string }> // true

因此,就打字稿而言,class Derived extends Base { }Base 相同。事实上,只要实例具有与文字相同的形状,类实例和对象文字就被认为是相同的。


也就是说,如果派生类在任何方面都不同,那么您可以在帮助中注意到如果此 Exact 类型并禁止它。

首先,您可以创建一个类型来强制类型完全匹配,例如:

type EnforceExact<Constraint, T> = Exact<Constraint, T> extends true ? T : never

然后让你的函数使用这个助手:

function doSomething<T extends IBase>(obj: EnforceExact<IBase, T>) {
    // do something
}

现在,如果我们以某种方式使 Derived 类不同:

class DerivedWithAddition extends Base {
  foo: number = 123
}

然后当我们尝试传入这个时,我们会得到一个类型错误:

doSomething(new DerivedWithAddition()) // not assignable to parameter of type 'never'.

Playground


您也可以使用 the answer that @kaya3 linked 中建议的 brand 解决方法。但是,除非有很好的理由,否则您可能不应该这样做。这种结构等效性是打字稿的一个非常棒的特性。只要有正确的界面,只要让某人传递他们想要的任何东西,你的生活就会更轻松。

相关问题