Emitor událostí s Typescriptem – pokročilé použití

Předávání funkčních rekvizit komponentám velkého dítěte a následné vyvolání funkce u některých změn dat je hektické a má vedlejší účinky. Takové předávání rekvizit také není příliš dobrý způsob, jak psát kód reakce nebo strojopisu.

Zde přichází emitor událostí. Event Emitter je běžný termín, pokud pracujete s projektem Angular nebo NodeJs, ale pokud jde o reakci, vývojáři tento termín téměř neslyšeli. Dovolte mi tedy rovnou k tomu, co je emitor událostí, co dělá a jak dělá?

Co je emitor události?

Emitor události je vzor kódu, který naslouchá pojmenované události, spustí (nebo zavolá) funkci zpětného volání a poté tuto událost vyšle s hodnotou. Někdy se tomu říká model „pub/sub (publisher-subscriber)“ neboli posluchač.

Proč potřebujeme Emitor událostí?

Jsou velmi užitečné, když máte nějakou funkci, která se musí provést „kdykoli se stane tato jiná věc“, aniž by bylo nutné, aby tato funkce skončila nebo dokonce fungovala.

Co dělá Emitor událostí?

Event Emitter řeší komplexní obchodní řešení, která vyžadují vyvolání určité funkčnosti na základě změny v nějaké jiné věci.

Složitý případ použití:Mám komponentu prodejního příspěvku, ve které mohu zveřejnit nějaký název, cenu a obrázky a ostatní uživatelé mi mohou nabídnout cenu za nákup položky. Nyní si přeji vypsat všechny nabídky ve formě komentářů pod příspěvkem, pak to lze provést pomocí backendu.
Pokud někdo dává nabídku, uložte detail nabídky do tabulky nabídky a také uložte komentář User.name gives $offer v tabulce komentářů v databázi.

Nyní existuje záznam pro každou nabídku v tabulce komentářů, ale komponenta komentáře na frontendu to neví. Zde může pomoci emitor událostí. Kdykoli někdo dá nabídku, vydejte událost, abyste znovu načetli komentáře, a tím se vyřešil složitý problém zobrazit komentáře k nabídce, jakmile je nabídka podána.

Jak funguje Event Emitter?

Nyní přejděme k části kódování. Vím, že pro některé uživatele může být složité to pochopit, protože je to pokročilá reakce, ale přesto to v mnoha případech pomáhá.

1. Vytvoříme soubor eventEmitter.ts, který bude hlavní částí našeho emitoru událostí.

V tomto budeme mít výčet EventType, který bude sledovat události podobné typům akcí nastaveným jako proměnné v redux.

Pak tu máme komplexní objekt eventEmitter. Mnoho z vás by se divilo, že jsem vytvořil objekt, ve kterém provádím funkce, poměrně složitý, ale skvělý.

Máme vlastnosti událostí, které jsou v podstatě dalším objektem, který vypíše název události s příslušnými zpětnými voláními. Je pouze pro čtení, protože si nepřejeme, aby byl měněn vnější funkcí. Je to podobné jako při použití private modifikátor přístupu ve třídách.

Poté máme funkci odeslání, která odešle událost a zavolá zpětné volání pro každou událost.

Subscribe přihlásí událost s konkrétním zpětným voláním a Unsubscribe odhlásí událost, aby se zabránilo zbytečným voláním událostí.

export enum EventType {
  REFETCH_COMMENT = 'refetchComment',
}

/**
 * Event emitter to subscribe, dispatch, and unsubscribe to events.
 */
export const eventEmitter: {
  readonly events: Record<string, (() => void)[]>
  dispatch(eventType: EventType, uniqueSuffix: string | number): void
  subscribe(eventType: EventType, uniqueSuffix: string | number, callback: () => void): void
  unsubscribe(eventType: EventType, uniqueSuffix: string | number): void
  getEventKey(eventType: EventType, uniqueSuffix: string | number): string
} = {
  //This is event object to store events.
  events: {},
  //Internal function to get event name from type and suffix
  getEventKey(eventType: EventType, uniqueSuffix: string | number) {
    return `${eventType} ${uniqueSuffix}`
  },
  //This will dispatch the event and call the callback for every event.
  dispatch(event, uniqueSuffix) {
    const eventName = this.getEventKey(event, uniqueSuffix)
    if (!this.events[eventName]) return
    this.events[eventName].forEach((callback: () => void) => callback())
  },
  //This will subscribe the event with a specific callback
  subscribe(event, uniqueSuffix, callback) {
    const eventName = this.getEventKey(event, uniqueSuffix)
    if (!this.events[eventName]) this.events[eventName] = []
    if (!this.events[eventName]?.includes(this.events[eventName][0])) this.events[eventName]?.push(callback)
  },
  //This will unsubscribe the event to avoid unnecessary event calls
  unsubscribe(event, uniqueSuffix) {
    const eventName = this.getEventKey(event, uniqueSuffix)
    if (!this.events[eventName]) return
    delete this.events[eventName]
  },
}

2. Nyní v komponentě nabídky, kam bychom zasílali nabídky, odešleme události a odhlásíme události po odeslání takto:

eventEmitter.dispatch(EventType.REFETCH_COMMENT, uniqueSuffix)
eventEmitter.unsubscribe(EventType.REFETCH_COMMENT, uniqueSuffix)

3. Zde bychom událost přihlásili zpětným voláním v komponentě komentářů, která znovu načte komentáře.

 eventEmitter.subscribe(EventType.REFETCH_COMMENT, uniqueSuffix, () => fetchLatestPostComments())

Zde je fetchLatestPostComments funkcí, která znovu načte komentáře z backendu.

Takto jsme vyřešili složitý obchodní problém pomocí emitorů událostí.

I když s každodenním vývojem dochází ke vzniku mutací a tyto složité úkoly mohou být prováděny i balíčky jako React-Query.