理解TypeScript中的泛型约束扩展
我试图创建一个泛型函数,该函数接受基类的子类型,并返回解析为指定类实例的承诺。此示例显示了我试图实现的目标:理解TypeScript中的泛型约束扩展,typescript,generics,Typescript,Generics,我试图创建一个泛型函数,该函数接受基类的子类型,并返回解析为指定类实例的承诺。此示例显示了我试图实现的目标: class foo {} class bar extends foo {} const someBar = new bar(); function foobar<T extends foo>(): Promise<T> { return new Promise<T>(resolve => resolve(someBar)); // this
class foo {}
class bar extends foo {}
const someBar = new bar();
function foobar<T extends foo>(): Promise<T> {
return new Promise<T>(resolve => resolve(someBar)); // this doesn't compile
}
TS正确地推断结果是一个条,但它不允许我在函数中返回someBar
在我的泛型类方法处理this
的类中,我已经能够使用多态this
来实现这一点,但我不在类中
更新2
实际上,这不需要承诺来说明我在做什么。在这里,我进一步简化了(并省略了foo和bar的定义,因为这并没有改变):
正如你所看到的,我所做的是微不足道的。我只是尝试在不向下转换的情况下进行多态类型检查。您提供的代码不是泛型函数,因此您尝试将其作为泛型函数键入时走错了路 泛型函数需要处理各种数据类型,同时仍然能够指定函数不同部分中类型之间的关系。例如,您可以指定函数传递某种类型,然后返回相同类型的数组
function wrapInArray<T> (input: T): T[] {
return [T];
}
// To be used like:
const result1 = wrapInArray(1234); // result1 is a number array;
const result2 = wrapInArray('hello'); // result2 is a string array;
您的函数只做一件事:返回一个
承诺
。您需要指定的类型之间没有关联,并且没有可能的方法调用函数,这将导致函数返回除承诺以外的内容。因此,正确的类型是:
const someBar = new bar();
function foobar(): Promise<bar> {
return new Promise(resolve => resolve(someBar));
}
好的,我非常感谢这里的所有反馈——但我已经对此进行了研究,这里有一点值得一提。首先,我很惊讶有这么多人关注我的例子。我故意提供了一个非常简单的例子。这就是所谓的“最小复制”——我的简单示例显示了一个编译器错误,其中像我这样的人可能(当然是错误的)认为返回类型是受约束泛型类型的有效多态子类型。同样,大多数回答都没有错——但它们没有完全解决这个问题。我会尽量提供一个更完整的图片在这里
我将对我的最后一个示例进行一点细分:
class Foo { f() {} }
class Bar extends Foo { b() {} }
var someBar = new Bar();
function foobar<T extends Foo>(): T {
return someBar;
}
const mybar: Bar = foobar<Bar>();
这不是按原样编译的。正如在我上面的return someBar
中一样,TS不记得t在结构上与MdkObjectBase兼容。它的行为就像T可以是任何东西,因此我无法从Global.load(承诺)分配结果
有趣的是,如果_resolve返回GlobalResolver的子类,我可以将其指定为返回类型:resolve(keyPath:string):this
。这利用了TS中的“多态This”。它不是泛型,但它允许我从基类返回一个子类,而无需调用者向下转换。但是我需要用一种不同的类型来做这个,而且似乎没有办法
总而言之:TypeScript泛型中的类型约束约束函数可以接受的类型。但是它不会影响函数中的泛型类型——TypesScript选择忘记该信息。我相信这将是有用的,就像多态性一样,这有助于防止需要进行降级。而且这也不是史无前例的(正如我所描述的,我非常确定Swift工作中的通用约束)。但是,唉,这不是TS的创造者们所想象的,我相信他们的选择是有原因的。我只能沮丧了。这是一个关于泛型的常见误解。泛型参数由函数的调用者决定。函数不能决定泛型参数是什么。通过在这里返回someBar
,您的函数假设T
是bar
,它不一定是这样。我想我不理解约束的作用,如果它不约束类型=),它就是约束类型。不能使用任何t
调用该方法。泛型约束并不约束方法实现(它实际上允许您在实现中执行更多操作),而是约束调用者。@EricS如果您使用纯javascript编写此函数,代码会是什么?(您给出了一个描述,但它与您提供的代码并不完全匹配)如果我们了解javascript,我们也许可以帮助您向其中添加类型信息。嗨@NicholasTower-我不确定纯javascript在这里会有帮助,因为我只是使用了一个简单的承诺-但尝试实现多态静态类型安全。但我确实编辑了上面的问题,并展示了一个非通用版本。此版本要求我向下转换结果。我在努力避免沮丧。这有意义吗?它不再将其视为“Foo的子类T”,而是“已知宇宙中的任何类型”
抱歉,但这是错误的。在函数内部,可以使用Foo的任何方法。你可以这样做,因为它肯定是一个Foo,没有更广泛的。但它也可能比foo更窄,所以不能使用任何需要更窄类型的东西(除非先进行类型检查)。因此,试图返回一个条比一个Foo更窄,typescript说“等等,有人可能会用
调用这个函数,在这种情况下,您返回了比他们期望的更窄的值”另一种看待问题的方式:有人可以用T=Qux调用函数。Qux从Foo扩展而来,它有一个Foo没有的额外属性(Bar也没有)。您列出的类型指定,由于T=Qux,将返回一个Qux。(或一个承诺
,取决于我们使用的示例)。但是您的代码无法保证额外属性是返回对象的一部分。您将返回Bar上的所有额外属性,但这对使用Qux调用它的人没有帮助。它应该很高兴地允许我这样做
function foobar<T extends Foo>(): T {
return someBar;
}
const mybar: Bar = foobar<Bar>();
var someBar = new bar();
function foobar() {
return someBar;
}
var myBar = foobar();
function wrapInArray<T> (input: T): T[] {
return [T];
}
// To be used like:
const result1 = wrapInArray(1234); // result1 is a number array;
const result2 = wrapInArray('hello'); // result2 is a string array;
function getLength<T extend { length: number }>(input: T): number {
// I can access input.length because even though i don't know what input's type is,
// i do know that it's illegal to call this function with anything that doesn't
// have a length property.
return input.length;
}
// to be used like:
const len1 = getLength([1, 2, 3]);
const len2 = getLength({ length: 5 });
const someBar = new bar();
function foobar(): Promise<bar> {
return new Promise(resolve => resolve(someBar));
}
function foobar<T extends Foo>(): T {
return someBar;
}
const mybar: Bar = foobar<Bar>();
function foobar(): Bar {
return someBar;
}
const myBar = foobar();
// You could add the type explicitly if you prefer, but it's not necessary:
// const myBar: Bar = foobar();
// And since anything that's a Bar necessarily has all the properties of a Foo, you can also do:
const myFoo: Foo = foobar();
class Foo { f() {} }
class Bar extends Foo { b() {} }
var someBar = new Bar();
function foobar<T extends Foo>(): T {
return someBar;
}
const mybar: Bar = foobar<Bar>();
class GlobalResolver extends CachingResolver {
protected _resolve<T extends MdkObjectBase>(
keyPath: string[]
): Promise<T> {
const globName = keyPath.join("/");
return Global.load(this._app, this._loadContext, `./Globals/${globName}`);
}
}