[Partten] PubSub partten & CustomEvent

PubSub is one of the most foundational patterns for reactivity. Firing an event out with publish() allows anyone to listen to that event subscribe() and do whatever they want in a decoupled from whatever fires that event.

type AnyFunction = (...args: any[]) => void

const pubSub = {
  events: {} as Record<PropertyKey, AnyFunction[]>,
  subscribe(eventName: string, callback: AnyFunction) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push(callback);
  },
  publish(eventName: string, data: any) {
    if (this.events[eventName]) {
      this.events[eventName].forEach((callback) => callback(data));
    }
  }
};

pubSub.subscribe("update", (data) => console.log(data));
pubSub.publish("update", "Update payload");

 

This is a simplest way to fulfill basic reactitivy partten.

Note the publisher has no idea of what is listening to it, so there is no way to unsubscribe or clean up after itself with this simple implementation.

 


 

In browser, there is API for firing and subscribing to custom events. It allows you to send data along with the custom events using dispatchEvent.

const pizzaEvent = new CustomEvent("pizzaDelivery", {
    detail: {
        name: "supreme"
    }
})
const handler = (e: WindowEventMap['pizzaDelivery']) => console.log(e.detail.name)
window.addEventListener('pizzaDelivery', handler)
window.dispatchEvent(pizzaEvent)
window.removeEventListener('pizzaDelivery', handler)

// global.d.ts

interface PizzaDeliveryEvent {
    name: string
}
interface CustomEventMap {
    "pizzaDelivery": CustomEvent<PizzaDeliveryEvent>
}
interface WindowEventMap extends CustomEventMap {

}

 

Class Instance Custom Events: Subclassing EventTarget

We can subclass EventTarget to send out events on a class instance for our app to bind to:

class PizzaStore extends EventTarget {
    constructor() {
      super();
    }
    sendPizza(pizza: string) {
      // fire event directly on the class
      this.dispatchEvent<"pizzaDelivery">(new CustomEvent("pizzaDelivery", {
        detail: {
          name: pizza,
        },
      }));
    }
  }

  const Pizzas = new PizzaStore();
  const handler2 = (e: CustomEventMap["pizzaDelivery"]) => console.log('Added Pizza:', e.detail.name)
  Pizzas.addEventListener("pizzaDelivery", handler2);
  Pizzas.sendPizza("supreme");
  Pizzas.removeEventListener("pizzaDelivery", handler2);

// global.d.ts

interface PizzaDeliveryEvent {
    name: string
}
interface CustomEventMap {
    "pizzaDelivery": CustomEvent<PizzaDeliveryEvent>
}
interface WindowEventMap extends CustomEventMap {

}
interface EventTarget {
    dispatchEvent<EventName extends keyof CustomEventMap>(event: CustomEventMap[EventName]): boolean;
    addEventListener<Type extends keyof CustomEventMap, T extends CustomEventMap[Type]>(type: Type, callback: (event: T) => void): void;
    removeEventListener<Type extends keyof CustomEventMap, T extends CustomEventMap[Type]>(type: Type, callback: (event: T) => void): void;
}

The cool thing about this is your events aren’t firing globally on the window. You can fire an event directly on a class; anything in your app can wire up event listeners directly to that class.

posted @ 2023-10-04 02:19  Zhentiw  阅读(5)  评论(0编辑  收藏  举报