我有一个基类型Base
,我想创建一个对象,该对象包含接收Base
(在这种情况下为ExtendsBaseA
或ExtendsBaseB
)的子类型的函数,并且将其映射到另一种类型C
。
我已尝试将" Base
&#34; 的某些子类型声明为<T extends Base>
,但类型检查失败,并显示以下内容:
Type&#39;(baseA:ExtendsBaseA)=&gt; C&#39;不能分配类型&#39;(base:T)=&gt; C&#39;
参数类型&#39; base&#39;和&#39; baseA&#39;是不相容的。
键入&#39; T&#39;不能分配给&#39; ExtendsBaseA&#39;。
键入&#39; Base&#39;不能分配给&#39; ExtendsBaseA&#39;。
物业&#39; b&#39;类型&#39; Base&#39;
中缺少
段
interface Base {
a: string
}
interface ExtendsBaseA extends Base {
b: string
}
interface ExtendsBaseB extends Base {
c: string
}
interface C {}
class Foo {
private readonly handlers: {
[key: string]: <T extends Base> (base: T) => C
}
constructor() {
this.handlers = {
'bar' : this.handler
}
}
handler(baseA: ExtendsBaseA): C {
return <C>null;
}
}
关于如何解决这个问题的任何想法?
修改:如果我改变了,那就太奇怪了:
[key: string]: <T extends Base> (base: T) => C
为:
[key: string]: (base: Base) => C
它在操场上工作,但在我本地的Typescript安装上尝试时却没有。 (均为2.9.1)
答案 0 :(得分:2)
由于您计划将handlers
作为一个映射,其键对应于被区分联合的判别式,因此您应该能够准确地表示该类型。让我充实你必须包括判别式的类型:
interface Base {
type: string
a: string
}
interface ExtendsBaseA extends Base {
type: "ExtendsBaseA"
b: string
}
interface ExtendsBaseB extends Base {
type: "ExtendsBaseB"
c: string
}
interface C { }
type BaseUnion = ExtendsBaseA | ExtendsBaseB;
请注意,您需要明确声明联合,如上面的BaseUnion
所示。现在我们可以按如下方式定义类型HandlerMap
。
type HandlerMap = {
[K in BaseUnion['type']]?: (base: Extract<BaseUnion, { type: K }>) => C
}
如果你检查一下,它看起来像是:
type HandlerMap = {
ExtendsBaseA?: (base: ExtendsBaseA) => C,
ExtendsBaseB?: (base: ExtendsBaseB) => C
}
现在您可以像这样定义Foo
类:
class Foo {
private readonly handlers: HandlerMap;
constructor() {
this.handlers = {
ExtendsBaseA: this.handler // changed the key
}
}
handler(baseA: ExtendsBaseA): C {
return <C>null!;
}
}
就这一切而言,一切正常。尽管如此,你会发现编写一个类型安全函数会感到很沮丧,该函数需要HandlerMap
和BaseUnion
并尝试生成C
:
function handle<B extends BaseUnion>(h: HandlerMap, b: B): C | undefined {
const handler = h[b.type]
if (!handler) return;
return handler(b); // error, no valid call signature
}
TypeScript编译器的控制流分析不够复杂,无法理解h[b.type]
的参数将始终与b
的类型完全对应。相反,它会发现h[b.type]
接受 {em} 成分BaseUnion
,而b
BaseUnion
成员function handle<B extends BaseUnion>(h: HandlerMap, b: B): C | undefined {
const handler = h[b.type] as ((b: B) => C) | undefined;
if (!handler) return;
return handler(b); // okay
}
并且他们认为他们不匹配的可能性。你可以声称他们确实匹配,这可能是你能做的最好的事情:
US Dollar
希望有所帮助。祝你好运!
答案 1 :(得分:2)
我对拥有一个知道所有扩展类/接口的联合类的建议要求不满意,因此我进一步研究了解决该问题的方法。
值得注意的是,该错误是相对合法的-系统无法知道我们在做什么总是有效的,因此它告诉我们了。因此,我们基本上必须解决类型安全问题-jcalz使用as ((b: B) => C)
我从另一个方向着手-告诉编译器内部我知道我在注册时使用类型安全,而不是在回调时在做什么,但我仍然想正确键入从基类派生的处理程序。
所以我们开始:
注册/调度程序:
interface Base {
type: string;
}
export type Handler<T extends Base> = (action: T) => void;
type InternalHandler = (action: Base) => void;
export class Dispatcher {
private handlers: { [key: string]: InternalHandler } = {};
On<T extends Base>(type: string, extHandler: Handler<T>) {
const handler = extHandler as InternalHandler;
this.handlers[type] = handler;
}
Dispatch(e: Base) {
const handler = this.handlers[e.type];
if(handler) handler(e);
}
}
事件:
export class ExtendsBaseFn implements Base {
type: 'Fn' = 'Fn';
constructor(public cb: () => void) { };
}
export class ExtendsBaseNN implements Base {
type: 'NN' = 'NN';
constructor(
public value1: number,
public value2: number,
) { }
}
处理程序:
export class ObjWithHandlers {
constructor() {
global.dispatcher.On('Fn', (baseFn: ExtendsBaseFn) => {
baseFn.cb();
});
global.dispatcher.On('NN', (baseNN: ExtendsBaseNN) => {
console.log(baseNN.value1 * baseNN.value2);
});
}
}
驾驶员:
(global as any).dispatcher = new Dispatcher();
const placeholder = new ObjWithHandlers();
最后是用户:
global.dispatcher.Dispatch(new ExtendsBaseFn(() => console.log('woo')));
global.dispatcher.Dispatch(new ExtendsBaseNN(5, 5));
注释:
{ type: 'xxx' }
或各个变量名。太方便了!通过静态访问器获得一些乐趣,甚至可以更安全地输入
export interface IDerivedClass<T> {
type: string;
}
export class Base {
static type: string;
static Type<T>(ext: IDerivedClass<T>): string {
return ext.type;
}
static TypeOf(inst: Base): string {
const ctor = inst.constructor;
return Base.Type(ctor);
}
type: string;
"constructor": typeof Base;
constructor() {
this.type = Base.TypeOf(this);
}
}
export class ExtendsBaseNN extends Base
{
static type = 'NN';
...
}
dispatcher.On(ExtendsBaseNN.type, () => {})