Události vs. Akce v Ember.js

Nedávno jsem pracoval s některým z mého týmu na komponentě Ember, která potřebovala reagovat na události JavaScriptu, vyjádřili určité nejasnosti ohledně rozdílu mezi událostmi JavaScript a systémem Ember's Action. Rozhodl jsem se sepsat základy zde.

Vyfukování bublin

Jedním ze základních chování událostí DOM JavaScriptu je probublávání. Zaměřme se na click událost, ačkoli typ události je libovolný. Předpokládejme, že máme HTML stránku složenou takto:

<html>
<body>
  <main>
    <p>Is TimeCop a better time travel movie than Back To The Future?</p>
    <button>Yes</button>
    <button>No</button>
    <button>Tough Call</button>
  </main>
</body>
</html>

Předpokládejme, že načtu tuto stránku do svého prohlížeče a kliknu na tlačítko „Tough Call“ (jedna ze tří správných odpovědí na této stránce), pak prohlížeč přejde po DOM a najde prvek pod ukazatelem myši. Podívá se na kořenový prvek, zkontroluje, zda jsou souřadnice události kliknutí v oblasti tohoto prvku, pokud ano, iteruje potomky elementu opakováním testu, dokud nenajde element, který obsahuje souřadnice události a nemá žádné potomky. V našem případě je to poslední button prvek na obrazovce.

Jakmile prohlížeč identifikuje prvek, na který se klikalo, zkontroluje, zda má nějaké posluchače události kliknutí. Ty lze přidat pomocí onclick Atribut HTML (nedoporučuje se), nastavení onclick vlastnost objektu prvku (také se nedoporučuje) nebo pomocí addEventListener prvku metoda. Pokud jsou na prvku, který jsou volány, přítomny obslužné rutiny událostí, jeden po druhém, dokud jeden z ovladačů neřekne události, aby se přestala šířit, událost není zrušena nebo nám dojdou ovladače událostí. Prohlížeč se poté přesune k nadřazenému prvku a opakuje proces, dokud nebude událost zrušena nebo dokud nám nedojdou nadřazené prvky.

Jak to zvládnout

Obslužné rutiny událostí jsou jednoduché funkce javascriptu, které přijímají jeden argument události (kromě onerror který získá další argumenty). Dokumentace obsluhy událostí MDN je velmi důkladná, měli byste si ji přečíst.

Existuje několik záludných faktorů, které zahrnují návratovou hodnotu funkce; pravidlo je, že pokud chcete událost zrušit, vraťte true jinak nevracej vůbec nic. beforeunload a error handlery jsou výjimkou z tohoto pravidla.

Trochu méně konverzace

Akce Ember mají podobnou koncepci jako události a jsou spouštěny událostmi (click standardně), ale šíří se jiným způsobem. První pravidlo Emberu je „data dolů, akce nahoru“. To znamená, že data přicházejí „dolů“ z tras (přes jejich model háčky) přes ovladač a do pohledu. Pohled vydává akce, které probublávají „nahoru“ přes ovladač do tras.

Podívejme se na jednoduchý příklad. Nejprve router:

import Router from '@ember/routing/router';

Router.map(function() {
  this.route('quiz', { path: '/quiz/:slug'})
});

export default Router;

Nyní naše kvízová trasa:

import Route from '@ember/routing/route';

export default Route.extend({
  model({ slug }) {
    return fetch(`/api/quizzes/${slug}`)
      .then(response => response.json());
  }
});

Nyní naše šablona kvízu:

<p>{{model.question}}</p>
{{#each model.answers as |answer|}}
  <button {{action 'selectAnswer' answer}}>{{answer}}</button>
{{/each}}

Stručná stránka o směrování

Když načteme naši stránku s kvízem, Ember nejprve zadá application route a volá to model háček. Protože jsme v naší aplikaci nedefinovali aplikační trasu, Ember pro nás vygeneruje výchozí, která nevrací nic ze svého modelu. Za předpokladu, že jsme zadali /quiz/time-travel-movies URI router poté zadá quiz route a zavolejte model hook, o kterém předpokládáme, že vrací reprezentaci JSON našeho kvízu. To znamená, že obě application a quiz trasy jsou zároveň "aktivní". To je docela výkonná funkce Emberu, zvláště jakmile začnou být trasy hluboce vnořeny.

Více bublin

Když je spuštěna akce, Ember ji probublává po řetězu; nejprve na ovladač kvízu, poté na quiz route a poté k rodičovské trase a tak dále, dokud nenajde obsluhu akce nebo nedosáhne aplikační trasy. Toto bublání je docela cool, protože to znamená, že můžeme zpracovávat běžné akce v horní části stromu trasy (například akce přihlášení nebo odhlášení) a konkrétnější akce na místech, kde jsou potřeba.

Zejména Ember vyvolá chybu, pokud nemáte handler pro akci, takže v našem příkladu výše to exploduje, protože nezpracováváme naše selectAnswer v ovladači nebo na trase.

Osamělá složka

Motto Ember „data dolů, akce nahoru“ se hroutí na úrovni komponent. Komponenty Ember by měly být atomové jednotky stavu uživatelského rozhraní, které nemají vedlejší účinky. To znamená, že naše možnosti pro vydávání akcí z komponent jsou záměrně omezené. Akce se chovají přesně tak, jak byste v rámci komponenty očekávali, až na to, že nedochází k žádnému bublání. To znamená, že akce specifikované v šabloně komponenty, které nemají odpovídající definici v javascriptu komponenty, způsobí, že Ember vyvolá chybu.

Hlavním způsobem, jak povolit komponentám vysílat akce, je použít to, co ember nazývá "akce uzavření" k předání vaší akce jako volatelné funkce na známé vlastnosti vaší komponenty, například:

{{my-button onSelect=(action 'selectAnswer' answer) label=answer}}
import Component from '@ember/component';
import { resolve } from 'rsvp';

export default Component({
  tagName: 'button',
  onSelect: resolve,

  actions: {
    selectAnswer(answer) {
      return this.onSelect(answer);
    }
  }
});

To je zvláště dobré, protože komponentu můžete znovu použít na jiných místech, aniž byste ji museli upravovat pro nové případy použití. Tato myšlenka je adaptací vzoru vkládání závislosti.

Případná komponenta

Existují tři hlavní způsoby, jak mohou komponenty reagovat na události prohlížeče. Nejjednodušší je použít action pomocník řídítek, který reaguje na vaši konkrétní událost, například:

<div {{action 'mouseDidEnter' on='mouseEnter'}} {{action 'mouseDidLeave' on='mouseLeave'}}>
  {{if mouseIsIn 'mouse in' 'mouse out'}}
</div>

Jak vidíte, může to být trochu nepraktické, když reagujete na spoustu různých událostí. Také to nefunguje skvěle, pokud chcete, aby na události reagovala celá vaše komponenta, nejen její prvky.

Druhým způsobem, jak zajistit, aby vaše komponenta reagovala na události, je definovat zpětná volání ve vaší komponentě. To se provádí definováním metody na komponentě s názvem události, kterou chcete zpracovat. Škoda, pokud jste chtěli mít vlastnost s názvem click nebo submit . Jsou dvě věci, které potřebujete vědět o obslužných rutinách událostí komponent; jejich jména jsou kamilizovaná (úplný seznam zde) a návratové typy jsou normalizovány. Vraťte false pokud chcete akci zrušit. Vrácení čehokoli jiného nemá žádný účinek.

import Component from '@ember/component';

export default Component({
  mouseIsIn: false,

  mouseDidEnter(event) {
    this.set('mouseIsIn', true);
    return false;
  },

  mouseDidLeave(event) {
    this.set('mouseIsIn', false);
    return false;
  }
});

Třetím způsobem je použití didInsertElement a willDestroyElement zpětná volání životního cyklu komponenty pro ruční správu vašich událostí, když je komponenta vložena a odebrána z DOM.

export default Component({
  mouseIsIn: false,

  didInsertElement() {
    this.onMouseEnter = () => { this.set('mouseIsIn', true); };
    this.onMouseLeave = () => { this.set('mouseIsIn', false); };
    this.element.addEventListener('mouseenter', this.onMouseEnter);
    this.element.addEventListener('mouseleave', this.onMouseLeave);
  },

  willRemoveElement() {
    this.element.removeEventListener('mouseenter', this.onMouseEnter);
    this.element.removeEventListener('mouseleave', this.onMouseLeave);
  }
});

Všimněte si, že pomocí jedné z posledních dvou metod můžete použít this.send(actionName, ...arguments) spouštět události na vaší komponentě, pokud si myslíte, že je to čistší.

Závěr

Jak vidíte, akce a události jsou podobné, ale odlišné. Na nejzákladnější úrovni se události používají k provádění změn v uživatelském rozhraní stav a akce se používají k provádění změn v aplikaci Stát. Jako obvykle to není pevné a rychlé pravidlo, takže když se ptáte sami sebe, zda byste měli použít události nebo akce, stejně jako u všech ostatních technických otázek, správná odpověď je „to záleží“.