简体   繁体   中英

How to implement the publish-subscribe pattern in TypeScript?

I'm working on creating an events system for my game, and my code currently looks like this:

export const enum ET { Collision, Dying, Damage }

type ActionCallback = (scene: Scene, event: GameEvent) => void;

subscribe(eventType: ET, callback: ActionCallback) {
  this.subscriptions[eventType].push(callback);
}

And then an example of some code which uses this function is like this:

scene.events.subscribe(ET.Dying, handleEntityDeath);

handleEntityDeath = (scene: Scene, event: DyingEvent) => {
  scene.deleteEntity(event.entity);
}

The problem is that TypeScript fails to compile and says something like: event's type must be GameEvent and not DyingEvent .

Basically, I need a way to "link" ET.Dying and DyingEvent , but I'm not sure how to do it. I think if I can accomplish this, then I can event handlers like the above where it will only compile if the first parameter is something like ET.Dying and the second parameter is a callback which takes a DyingEvent . I would want it to fail to compile if the callback instead had a DamageEvent parameter, if that makes sense.

Can this be done, and if so, how?

Figured it out:

interface EventMap {
  [ET.Collision]: CollisionEvent;
  [ET.Dying]: DyingEvent;
  // etc
}

subscribe = <T extends ET>(eventType: T, callback: (scene: Scene, event: EventMap[T]) => void) => {
  this.subscriptions[eventType].push(callback);
}

// Example calling code below here:

scene.events.subscribe(ET.Dying, handleEntityDeath);

handleEntityDeath = (scene: Scene, event: DyingEvent) => {
  scene.deleteEntity(event.entity);
}

Edit: TS Playground example

First, create a new file: PubSub.ts :

/**
 * Defines the function type of the publish function.
 *
 * Extracts the keys from `E` as valid event types, and the matching
 * property as the payload.
 */
type PubTypeFn<E> = <Key extends string & keyof E>(
    event: Key,
    message: E[Key]
) => void

/**
 * Defines the function type for the subscribe function.
 *
 * Extracts the keys from `E` as valid event types, and the matching
 * property as the payload to the callback function.
 */
type SubTypeFn<E> = <Key extends string & keyof E>(
    event: Key,
    fn: MessageFn<E>
) => void

/**
 * Defines the function type for the subscription callback. Ensures
 * the message payload is a valid property of the event being used.
 */
type MessageFn<E> = <Key extends string & keyof E>(message: E[Key]) => void

/**
 * Tie everything together.
 */
type PubSubType<E> = {
    publish: PubTypeFn<E>,

    subscribe: SubTypeFn<E>
    unsubscribe: SubTypeFn<E>
}

/**
 * Creates a new PubSub instance, the `E` type parameter should be a
 * type enumerating all the available events and their payloads.
 *
 * @example
 * type Events = {
 *  warn: { message: string },
 *  error: { message: string }
 * }
 *
 * const pubSub = PubSub<Events>() 
 * pubSub.publish('warn', { message: "Something bad happened!" })
 */
export function PubSub<E>(): PubSubType<E> {

    const handlers: { [key: string]: (MessageFn<E>)[] } = {}

    return {
        publish: (event, msg) => {
            handlers[event].forEach(h => h(msg))
        },

        subscribe: (event, callback) => {
            const list = handlers[event] ?? []
            list.push(callback)
            handlers[event] = list
        },

        unsubscribe: (event, callback) => {
            let list = handlers[event] ?? []
            list = list.filter(h => h !== callback)
            handlers[event] = list
        }
    }
}

Next, create another file where you will use the PubSub func:

import { PubSub } from './PubSub'


type events = {
  CreatedPerson: { id: string, name: string }
  DeletedPerson: { personId: string; reason: string }
}

const pubSub = PubSub<events>()

pubSub.publish("CreatedPerson", { id: '1', name: 'cory' })

pubSub.subscribe("CreatedPerson", (message) => {
    console.log(message)
})

ref:https://gist.github.com/sidola/3b267f21c872e449ef4bbdae9e2baeab

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM