Typescript-具有绑定泛型类型的函数';不可转让';到扩展类型
我有一个基本类型Typescript-具有绑定泛型类型的函数';不可转让';到扩展类型,typescript,generics,types,Typescript,Generics,Types,我有一个基本类型base,我想创建一个对象,该对象包含接收base某些子类型的函数(ExtendsBaseA或ExtendsBaseB),并将其映射到另一个类型C 我已尝试将“Base的某些子类型”声明为,但类型检查失败,原因如下: 类型'(baseA:ExtendsBaseA)=>C'不可分配给类型'(base:T)=>C' 参数“base”和“baseA”的类型不兼容 类型“T”不可分配给类型“ExtendsBaseA” 类型“Base”不可分配给类型“ExtendsBaseA” 类型“Ba
base
,我想创建一个对象,该对象包含接收base
某些子类型的函数(ExtendsBaseA
或ExtendsBaseB
),并将其映射到另一个类型C
我已尝试将“Base的某些子类型”声明为
,但类型检查失败,原因如下:
类型'(baseA:ExtendsBaseA)=>C'不可分配给类型'(base:T)=>C'
参数“base”和“baseA”的类型不兼容
类型“T”不可分配给类型“ExtendsBaseA”
类型“Base”不可分配给类型“ExtendsBaseA”
类型“Base”中缺少属性“b”
片段
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;
}
}
它可以在操场上使用,但当我在本地的Typescript安装上尝试它时就不行了。(两者都是2.9.1)由于您计划将
处理程序
作为一个映射,其键对应于受歧视的联合的判别式,因此您应该能够准确地表示该类型。让我来充实一下,你必须包括判别式的类型:
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
}
现在您可以这样定义Foo
类:
class Foo {
private readonly handlers: HandlerMap;
constructor() {
this.handlers = {
ExtendsBaseA: this.handler // changed the key
}
}
handler(baseA: ExtendsBaseA): C {
return <C>null!;
}
}
TypeScript编译器的控制流分析不够复杂,无法理解h[b.type]
的参数始终与b
的类型完全对应。相反,它看到h[b.type]
接受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
}
函数句柄(h:HandlerMap,b:b):C |未定义{
常量handler=h[b.type]as((b:b)=>C)|未定义;
如果(!handler)返回;
返回处理程序(b);//好的
}
希望这能有所帮助。祝你好运 我不满意关于拥有一个了解所有扩展类/接口的联合类的建议要求,所以我进一步研究了解决这个问题的方法 值得注意的是,错误是相对合法的——系统无法知道我们所做的总是有效的,所以它告诉我们是这样的。因此,我们基本上必须围绕类型安全展开工作-就像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 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));
注:
- 添加到全局/占位符中,以减少示例
- 我喜欢将事件定义作为类,以实现良好的构造函数、易用性、清晰性和安全性。与接口+create函数相反,它让每个人自己编写
或单个变量名。太方便了李>{type:'xxx'}
- C在这个最小的例子中是无效的。函数的返回类型是这里实际类型问题的辅助
- 静态成员类型(v3-超级简化)
- 子类上的typeguard检查(没有父/子依赖项)
导出接口IDerivedClass{
类型:字符串;
新(…args:any[]):T;
}
导出类基{
//不支持静态抽象-别忘了在派生类中添加它!
//静态类型:字符串;
类型:string=(this.constructor作为IDerivedClass);
Is(ctor:IDerivedClass):这是T{
返回(this.type==ctor.type);
}
}
导出类ExtendsBaseNN扩展基
{
静态类型='NN';
...
}
//显示调度器的易用性
dispatcher.On(ExtendsBaseNN.type,(nn:ExtendsBaseNN)=>{…})
//显示typeguard:
const obj:Base=新扩展的basenn(5,5);
如果(obj.Is(extendsbasernn))返回obj.value1*obj.value2;
在操场上打开StricFunctionTypes
以查看错误。您打算如何使用处理程序
?如果每个属性都是只处理Base
的某些子类型而不是Base
的所有子类型的函数,则无法使用任何参数调用它(因为您永远不知道您拥有的Base
对象是否是该函数的“正确”对象)。要么让函数接受Base
的所有子类型,要么用类型安全的方法将每个Base
分派给适当的处理函数。如果您能详细说明您计划如何实际使用处理程序
,我可能会提出一个具体的建议。@jcalz感谢您的建议。因此,我有不同的Base
子类型,我希望以不同的方式处理它们,并且Base
上的type
string属性帮助我唯一地区分它们。我想使用Base.type
string:this.handlers[obj.type](obj)
@jcalz调度相应的handler
函数,我想这些被称为“有区别的联合”,typescript可以使用类型保护来处理它们。然而,在这种情况下,我只想阻止类型系统的阻碍,让我通过索引映射来完成。我想我可以做[key:string]:(base:any)=>C
,但是any
觉得太过宽容了,因为我的函数参数总是base
的子类型。它只是觉得有办法做到这一点谢谢,我花了一些时间去理解Extract
做了什么,但是得到了答案
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));
export interface IDerivedClass<T extends Base> {
type: string;
new (...args: any[]): T;
}
export class Base {
// no support for static abstract - don't forget to add this in the derived class!
// static type: string;
type: string = (this.constructor as IDerivedClass<this>).type;
Is<T extends IDerivedClass>(ctor: IDerivedClass<T>): this is T {
return (this.type === ctor.type);
}
}
export class ExtendsBaseNN extends Base
{
static type = 'NN';
...
}
// show dispatcher ease of use
dispatcher.On(ExtendsBaseNN.type, (nn: ExtendsBaseNN) => {...})
// show typeguard:
const obj: Base = new ExtendsBaseNN(5,5);
if (obj.Is(ExtendsBaseNN)) return obj.value1 * obj.value2;