首页 > 解决方案 > 事件映射和类型保护

问题描述

假设我正在为聊天应用程序构建一个事件管理器。我之前使用过事件地图并且玩得很开心,所以我会再试一次。

这是活动地图:

interface ChatEventMap {
    incomingMessage: string;
    newUser: {
        name: string;
        id: number;
    };
}

type EvType = keyof ChatEventMap;
type Ev<T extends EvType> = ChatEventMap[T];

因此,为了监听一个事件,我们有一个on接收两个参数的函数:type事件的(或名称),以及一个被称为传递事件数据的回调。

是这样的:

function on<T extends EvType>(type: T, callback: (data: Ev<T>) => void) {
    // do stuff
}

on('newUser', user => {
    console.log(user.name); // No errors!
});

但现在我需要一次收听所有事件。我想制作一个onEvent只接收事件类型及其数据的回调的函数。

问题是在回调函数内部,类型保护不起作用!

function onEvent(callback: <T extends EvType>(
    ev: { type: T; data: ChatEventMap[T] },
) => void) {
    // do stuff
}

onEvent(ev => {
    if (ev.type === 'newUser') {
        console.log(ev.data.name); // Error: Property 'name' does not exist on type 'ChatEventMap[T]'
    }
});

我究竟做错了什么?

TS游乐场

标签: typescript

解决方案


在这里你有工作代码

interface ChatEventMap {
  incomingMessage: string;
  newUser: {
    id: number;
    name: string;
  };
}

type EvType = keyof ChatEventMap;
type Ev<T extends EvType> = ChatEventMap[T];

// a bit of love for TypeScript
type Mapped = {
  [P in keyof ChatEventMap]: {
    type: P, data: ChatEventMap[P]
  }
}


function on<T extends EvType>(type: T, callback: (data: Ev<T>) => void) {
  // do stuff
}

on('newUser', user => {
  console.log(user.name); // No errors!
});

function onEvent(callback: (ev: Mapped[EvType]) => void) { }

onEvent(ev => {
  if (ev.type === 'newUser') {
    console.log(ev.data.name); // ok
  }
  if (ev.type === 'incomingMessage') {
    console.log(ev.data); // ok, string
  }
});

有时,泛型并不是最好的解决方案。

请记住,接下来的两个函数是不一样的:

function onEvent(callback: <T extends EvType>(ev: Mapped[T]) => void) { }

function onEvent(callback: (ev: Mapped[EvType]) => void) { }

推荐阅读