TS playground

我违反了哪条TS规则?我要怎么重写呢?

interface Event1 {
    name: 'Event1'
    age: number
}

interface Event2 {
    name: 'Next2'
    lastName: string
}

type UnionEvent = Event1 | Event2


interface EventHandler<T extends UnionEvent> {
  matcher: (node: UnionEvent) => node is Extract<UnionEvent, T>;
  handler: (node: T) => void;
}
const listeners: EventHandler<UnionEvent>[] = [];

export const addEventHandler = <T extends UnionEvent>(
  handler: EventHandler<T>
) => {
  listeners.push(handler);
                    // ^ Argument of type 'EventHandler<T>' is not assignable to parameter of type 'EventHandler<UnionEvent>'. 
};

any的下一个变种,但它不被视为解决方案

Ts playground

                                            // any
interface EventHandler<T extends UnionEvent = any> {
  matcher: (node: UnionEvent) => node is Extract<UnionEvent, T>;
  handler: (node: T) => void;
}

// array of any 👎
const listeners: EventHandler[] = [];

export const addEventHandler = <T extends UnionEvent>(
  handler: EventHandler<T>
) => {
  listeners.push(handler);
};

我try 使用从基事件类继承

  interface BaseEvent {
    name: string
  }

interface Event1 extends BaseEvent {
    name: 'Event1'
    age: number
}

interface Event2 extends BaseEvent {
    name: 'Next2'
    lastName: string
}

interface EventHandler<T extends BaseEvent> {
  matcher: (node: UnionEvent) => node is Extract<UnionEvent, T>;
  handler: (node: T) => void;
}
const listeners: EventHandler<BaseEvent>[] = [];

export const addEventHandler = <T extends BaseEvent>(
  handler: EventHandler<T>
) => {
  listeners.push(handler);
                    // ^ Argument of type 'EventHandler<T>' is not assignable to parameter of type 'EventHandler<UnionEvent>'. 
};

推荐答案

整个错误消息如下所示:

Argument of type 'EventHandler<T>' is not assignable to parameter of type 'EventHandler<UnionEvent>'.
  Types of property 'handler' are incompatible.
    Type '(node: T) => void' is not assignable to type '(node: UnionEvent) => void'.
      Types of parameters 'node' and 'node' are incompatible.
        Type 'UnionEvent' is not assignable to type 'T'.
          'UnionEvent' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'UnionEvent'.
            Type 'Event1' is not assignable to type 'T'.
              'Event1' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'UnionEvent'.(2345)

最后一行很重要: 'Event1' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'UnionEvent'.

要理解这一信息,从集合的Angular 来考虑子类型是很有帮助的--子类型的所有值都属于超类型.

我可以很容易地找到Union Event的两个子类型-Event1和Event2.

在您的示例中,您传递了一个事件处理程序:

addEventHandler({
    matcher(event): event is Extract<UnionEvent, {name: 'Next2'}>
    {
        return true
    },
    handler(event2) {
        console.log(event2.name)
    }
})

也就是EventHandler<Event2> 你想在一个EventHandler<Event1 | Event2>的列表中存储

编译器对象,并且理所当然地这样做--想象一下以下代码:

listeners.forEach(listener => listener.handler({name: 'Event1', age: 1}));

您使用只能处理Event2的函数存储了一个对象,但发生了Event1,这会导致运行时错误. 为了防止这种情况,编译器会阻止存储.

现在,您已经知道了对handler的调用将在对match的调用之前,并且只有在类型正确时才会调用handler.

您可以将此知识传递给编译器:

type EventHandlerWithTypeTest = (node: UnionEvent) => void;

const makeHandlerWithTypeTest = <T extends UnionEvent>(handler: EventHandler<T>) => {
  return (node: UnionEvent): void => {
    if (handler.matcher(node)) {
      handler.handler(node);
    }
  }
}

const listeners: EventHandlerWithTypeTest[] = [];

const addEventHandler = <T extends UnionEvent>(
  handler: EventHandler<T>
) => {
  listeners.push(makeHandlerWithTypeTest(handler));
};

TS Playground

Typescript相关问答推荐

如何在不使用变量的情况下静态判断运算式的类型?

如何使用axios在前端显示错误体

有没有可能使用redux工具包的中间件同时监听状态的变化和操作

如何在TypeScrip中使用嵌套对象中的类型映射?

TypeError:正文不可用-NextJS服务器操作POST

推断从其他类型派生的类型

如何在Typescript 中组合unions ?

在React中定义props 的不同方式.FunctionalComponent

是否可以通过映射类型将函数参数约束为预定义类型?

将布尔值附加到每个对象特性

打字错误TS2305:模块常量没有导出的成员

是否将Angular *ngFor和*ngIf迁移到新的v17语法?

字幕子集一个元组的多个元素

仅当类型为联合类型时映射属性类型

Typescript不允许在数组中使用联合类型

如何正确键入泛型相对记录值类型?

如何使用useSearchParams保持状态

确保财产存在

如何将元素推入对象中的类型化数组

我应该使用服务方法(依赖于可观察的)在组件中进行计算吗?