Gli 8 errori più comuni commessi dagli sviluppatori di Ember.js

Pubblicato: 2022-03-11

Ember.js è un framework completo per la creazione di complesse applicazioni lato client. Uno dei suoi principi è "la convenzione sulla configurazione" e la convinzione che vi sia una parte molto ampia dello sviluppo comune alla maggior parte delle applicazioni Web, e quindi un unico modo migliore per risolvere la maggior parte di queste sfide quotidiane. Tuttavia, trovare la giusta astrazione e coprire tutti i casi richiede tempo e input da parte dell'intera comunità. Secondo il ragionamento, è meglio prendersi il tempo per trovare la soluzione giusta per il problema principale, e poi inserirlo nella struttura, invece di alzare le mani e lasciare che tutti si arrangino da soli quando hanno bisogno di trovare una soluzione.

Ember.js è in continua evoluzione per rendere lo sviluppo ancora più semplice. Ma, come con qualsiasi framework avanzato, ci sono ancora delle insidie ​​in cui gli sviluppatori di Ember possono cadere. Con il seguente post, spero di fornire una mappa per eludere questi. Entriamo subito!

Errore comune n. 1: aspettarsi che l'hook del modello si attivi quando tutti gli oggetti del contesto vengono passati

Supponiamo di avere i seguenti percorsi nella nostra applicazione:

 Router.map(function() { this.route('band', { path: 'bands/:id' }, function() { this.route('songs'); }); });

Il percorso band ha un segmento dinamico, id . Quando l'app viene caricata con un URL come /bands/24 , 24 viene passato all'hook model del percorso corrispondente, band . L'hook del modello ha il ruolo di deserializzare il segmento per creare un oggetto (o una matrice di oggetti) che può quindi essere utilizzato nel modello:

 // app/routes/band.js export default Ember.Route.extend({ model: function(params) { return this.store.find('band', params.id); // params.id is '24' } });

Fin qui tutto bene. Tuttavia, ci sono altri modi per inserire percorsi rispetto al caricamento dell'applicazione dalla barra di navigazione del browser. Uno di questi sta usando il link-to helper dai modelli. Il frammento di codice seguente esamina un elenco di bande e crea un collegamento ai rispettivi percorsi di band :

 {{#each bands as |band|}} {{link-to band.name "band" band}} {{/each}}

L'ultimo argomento per link-to, band , è un oggetto che riempie il segmento dinamico per il percorso, e quindi il suo id diventa il segmento id per il percorso. La trappola in cui cadono molte persone è che l'hook del modello non viene chiamato in quel caso, poiché il modello è già noto ed è stato passato. Ha senso e potrebbe salvare una richiesta al server ma, certamente, non lo è intuitivo. Un modo ingegnoso che consiste nel passare non l'oggetto stesso, ma il suo ID:

 {{#each bands as |band|}} {{link-to band.name "band" band.id}} {{/each}} 

Ember.js

Piano di mitigazione di Ember

I componenti instradabili arriveranno a breve su Ember, probabilmente nella versione 2.1 o 2.2. Quando atterrano, l'hook del modello verrà sempre chiamato, indipendentemente da come si passa a una rotta con un segmento dinamico. Leggi la RFC corrispondente qui.

Errore comune n. 2: dimenticare che i controller guidati dal percorso sono singleton

Le route in Ember.js impostano le proprietà sui controller che fungono da contesto per il modello corrispondente. Questi controller sono singleton e di conseguenza qualsiasi stato definito su di essi persiste anche quando il controller non è più attivo.

Questo è qualcosa che è molto facile da trascurare e anche io mi sono imbattuto in questo. Nel mio caso, avevo un'app di catalogo musicale con gruppi e canzoni. Il flag songCreationStarted sul controller dei songs indicava che l'utente ha iniziato a creare un brano per una particolare band. Il problema era che se l'utente passava a un'altra band, il valore di songCreationStarted persisteva e sembrava che la canzone semifinita fosse per l'altra band, il che creava confusione.

La soluzione è ripristinare manualmente le proprietà del controller che non vogliamo indugiare. Un posto possibile per farlo è l'hook setupController del percorso corrispondente, che viene chiamato in tutte le transizioni dopo l'hook afterModel (che, come suggerisce il nome, viene dopo l'hook model ):

 // app/routes/band.js export default Ember.Route.extend({ setupController: function(controller, model) { this._super(controller, model); controller.set('songCreationStarted', false); } });

Piano di mitigazione di Ember

Ancora una volta, l'alba dei componenti instradabili risolverà questo problema, ponendo fine del tutto ai controller. Uno dei vantaggi dei componenti instradabili è che hanno un ciclo di vita più coerente e vengono sempre demoliti quando si abbandonano i percorsi. Quando arrivano, il problema di cui sopra svanirà.

Errore comune n. 3: non chiamare l'implementazione predefinita in setupController

I percorsi in Ember hanno una manciata di hook del ciclo di vita per definire il comportamento specifico dell'applicazione. Abbiamo già visto il model che viene utilizzato per recuperare i dati per il modello corrispondente e setupController , per impostare il controller, il contesto del modello.

Quest'ultimo, setupController , ha un default sensato, che sta assegnando il modello, dall'hook del model come proprietà del model del controller:

 // ember-routing/lib/system/route.js setupController(controller, context, transition) { if (controller && (context !== undefined)) { set(controller, 'model', context); } }

( context è il nome utilizzato dal pacchetto ember-routing per quello che chiamo model sopra)

L'hook setupController può essere ignorato per diversi scopi, come il ripristino dello stato del controller (come nell'errore comune n. 2 sopra). Tuttavia, se si dimentica di chiamare l'implementazione padre che ho copiato sopra in Ember.Route, è possibile eseguire una lunga sessione di grattacapo, poiché il controller non avrà la proprietà del model impostata. Quindi chiama sempre this._super(controller, model) :

 export default Ember.Route.extend({ setupController: function(controller, model) { this._super(controller, model); // put the custom setup here } });

Piano di mitigazione di Ember

Come affermato in precedenza, i controller, e con essi l'hook setupController , scompariranno presto, quindi questa trappola non sarà più una minaccia. Tuttavia, c'è una lezione più grande da imparare qui, che è essere consapevoli delle implementazioni negli antenati. La funzione init , definita in Ember.Object , la madre di tutti gli oggetti in Ember, è un altro esempio a cui devi prestare attenzione.

Errore comune n. 4: utilizzo di this.modelFor con percorsi non padre

Il router Ember risolve il modello per ogni segmento di percorso mentre elabora l'URL. Supponiamo di avere i seguenti percorsi nella nostra applicazione:

 Router.map({ this.route('bands', function() { this.route('band', { path: ':id' }, function() { this.route('songs'); }); }); });

Dato un URL di /bands/24/songs , vengono chiamati gli hook del model di bands , bands.band e quindi bands.band.songs , in questo ordine. L'API route ha un metodo particolarmente utile, modelFor , che può essere utilizzato nelle route figlie per recuperare il modello da una delle route principali, poiché quel modello è stato sicuramente risolto a quel punto.

Ad esempio, il codice seguente è un modo valido per recuperare l'oggetto band nel percorso bands.band :

 // app/routes/bands/band.js export default Ember.Route.extend({ model: function(params) { var bands = this.modelFor('bands'); return bands.filterBy('id', params.id); } });

Un errore comune, tuttavia, è usare un nome di percorso in modelFor che non è un genitore del percorso. Se i percorsi dell'esempio sopra sono stati leggermente modificati:

 Router.map({ this.route('bands'); this.route('band', { path: 'bands/:id' }, function() { this.route('songs'); }); });

Il nostro metodo per recuperare la banda designata nell'URL si interromperebbe, poiché il percorso delle bands non è più un genitore e quindi il suo modello non è stato risolto.

 // app/routes/bands/band.js export default Ember.Route.extend({ model: function(params) { var bands = this.modelFor('bands'); // `bands` is undefined return bands.filterBy('id', params.id); // => error! } });

La soluzione consiste nell'utilizzare modelFor solo per le route principali e utilizzare altri mezzi per recuperare i dati necessari quando modelFor non può essere utilizzato, ad esempio il recupero dal negozio.

 // app/routes/bands/band.js export default Ember.Route.extend({ model: function(params) { return this.store.find('band', params.id); } });

Errore comune n. 5: erroneamente il contesto in cui viene attivata un'azione componente

I componenti nidificati sono sempre stati una delle parti più difficili di Ember su cui ragionare. Con l'introduzione dei parametri di blocco in Ember 1.10, gran parte di questa complessità è stata alleviata, ma in molte situazioni è ancora difficile vedere a colpo d'occhio su quale componente verrà attivata un'azione, avviata da un componente figlio.

Supponiamo di avere un componente band-list che contiene elementi band-list-items e possiamo contrassegnare ciascuna band come preferita nell'elenco.

 // app/templates/components/band-list.hbs {{#each bands as |band|}} {{band-list-item band=band faveAction="setAsFavorite"}} {{/each}}

Il nome dell'azione che deve essere richiamato quando l'utente fa clic sul pulsante viene passato al band-list-item e diventa il valore della relativa proprietà faveAction .

Vediamo ora il modello e la definizione del componente di band-list-item :

 // app/templates/components/band-list-item.hbs <div class="band-name">{{band.name}}</div> <button class="fav-button" {{action "faveBand"}}>Fave this</button>
 // app/components/band-list-item.js export default Ember.Component.extend({ band: null, faveAction: '', actions: { faveBand: { this.sendAction('faveAction', this.get('band')); } } });

Quando l'utente fa clic sul pulsante "Fave this", viene attivata l'azione faveBand , che attiva la faveAction del componente che è stata passata ( setAsFavorite , nel caso precedente), sul suo componente padre , band-list .

Questo fa inciampare molte persone poiché si aspettano che l'azione venga attivata nello stesso modo in cui lo sono le azioni dai modelli guidati dal percorso, sul controller (e quindi ribolle sui percorsi attivi). Ciò che lo rende peggiore è che non viene registrato alcun messaggio di errore; il componente padre ingoia semplicemente l'errore.

La regola generale è che le azioni vengono attivate nel contesto corrente. Nel caso di modelli non componenti, quel contesto è il controller corrente, mentre nel caso di modelli di componenti, è il componente padre (se presente) o ancora il controller corrente se il componente non è nidificato.

Quindi, nel caso precedente, il componente band-list dovrebbe riattivare l'azione ricevuta dall'elemento band-list-item per inviarlo al controller o al percorso.

 // app/components/band-list.js export default Ember.Component.extend({ bands: [], favoriteAction: 'setFavoriteBand', actions: { setAsFavorite: function(band) { this.sendAction('favoriteAction', band); } } });

Se l' band-list delle bande è stato definito nel modello delle bands , l'azione setFavoriteBand dovrebbe essere gestita nel controller delle bands o nella route delle bands (o in una delle relative route principali).

Piano di mitigazione di Ember

Puoi immaginare che ciò diventi più complesso se ci sono più livelli di nidificazione (ad esempio, avendo un componente fav-button all'interno band-list-item ). Devi praticare un foro attraverso diversi livelli dall'interno per far uscire il tuo messaggio, definendo nomi significativi ad ogni livello ( setAsFavorite , favoriteAction , faveAction , ecc.)

Ciò è reso più semplice da "Improved Actions RFC", che è già disponibile sul ramo principale e sarà probabilmente incluso in 1.13.

L'esempio precedente verrebbe quindi semplificato in:

 // app/templates/components/band-list.hbs {{#each bands as |band|}} {{band-list-item band=band setFavBand=(action "setFavoriteBand")}} {{/each}}
 // app/templates/components/band-list-item.hbs <div class="band-name">{{band.name}}</div> <button class="fav-button" {{action "setFavBand" band}}>Fave this</button>

Errore comune n. 6: utilizzo delle proprietà dell'array come chiavi dipendenti

Le proprietà calcolate di Ember dipendono da altre proprietà e questa dipendenza deve essere definita in modo esplicito dallo sviluppatore. Supponiamo di avere una proprietà isAdmin che dovrebbe essere vera se e solo se uno dei ruoli è admin . Ecco come si potrebbe scriverlo:

 isAdmin: function() { return this.get('roles').contains('admin'); }.property('roles')

Con la definizione precedente, il valore di isAdmin viene invalidato solo se l'oggetto array dei roles cambia, ma non se gli elementi vengono aggiunti o rimossi all'array esistente. Esiste una sintassi speciale per definire che le aggiunte e le rimozioni dovrebbero anche attivare un ricalcolo:

 isAdmin: function() { return this.get('roles').contains('admin'); }.property('roles.[]')

Errore comune n. 7: non utilizzare metodi compatibili con gli osservatori

Estendiamo l'esempio (ora corretto) dall'errore comune n. 6 e creiamo una classe User nella nostra applicazione.

 var User = Ember.Object.extend({ initRoles: function() { var roles = this.get('roles'); if (!roles) { this.set('roles', []); } }.on('init'), isAdmin: function() { return this.get('roles').contains('admin'); }.property('roles.[]') });

Quando aggiungiamo il ruolo di admin a un tale User , ci aspetta una sorpresa:

 var user = User.create(); user.get('isAdmin'); // => false user.get('roles').push('admin'); user.get('isAdmin'); // => false ?

Il problema è che gli osservatori non si attiveranno (e quindi le proprietà calcolate non verranno aggiornate) se vengono utilizzati i metodi Javascript di serie. Questo potrebbe cambiare se l'adozione globale di Object.observe nei browser migliora, ma fino ad allora, dobbiamo utilizzare l'insieme di metodi forniti da Ember. Nel caso attuale, pushObject è l'equivalente di push :

 user.get('roles').pushObject('admin'); user.get('isAdmin'); // => true, finally!

Errore comune n. 8: mutazione passata nelle proprietà nei componenti

Immagina di avere un componente star-rating che mostra la valutazione di un articolo e consente di impostare la valutazione dell'articolo. La valutazione può riguardare una canzone, un libro o l'abilità di palleggio di un giocatore di calcio.

Lo useresti in questo modo nel tuo modello:

 {{#each songs as |song|}} {{star-rating item=song rating=song.rating}} {{/each}}

Supponiamo inoltre che il componente visualizzi le stelle, una stella intera per ogni punto e successivamente le stelle vuote, fino a un punteggio massimo. Quando si fa clic su una stella, viene attivata un'azione set sul controller e dovrebbe essere interpretata come l'utente che desidera aggiornare la valutazione. Potremmo scrivere il seguente codice per ottenere questo:

 // app/components/star-rating.js export default Ember.Component.extend({ item: null, rating: 0, (...) actions: { set: function(newRating) { var item = this.get('item'); item.set('rating', newRating); return item.save(); } } });

Ciò porterebbe a termine il lavoro, ma ci sono un paio di problemi con esso. Innanzitutto, presuppone che l'elemento passato abbia una proprietà di rating , quindi non possiamo utilizzare questo componente per gestire l'abilità di palleggio di Leo Messi (dove questa proprietà potrebbe essere chiamata score ).

In secondo luogo, muta la valutazione dell'elemento nel componente. Questo porta a scenari in cui è difficile capire perché una determinata proprietà cambia. Immagina di avere un altro componente nello stesso modello in cui quella valutazione viene utilizzata anche, ad esempio, per calcolare il punteggio medio per il giocatore di calcio.

Lo slogan per mitigare la complessità di questo scenario è “Data down, actions up” (DDAU). I dati dovrebbero essere trasmessi (dal percorso al responsabile del trattamento ai componenti), mentre i componenti dovrebbero utilizzare azioni per notificare al proprio contesto le modifiche a questi dati. Quindi, come dovrebbe essere applicato DDAU qui?

Aggiungiamo un nome di azione che dovrebbe essere inviato per aggiornare la valutazione:

 {{#each songs as |song|}} {{star-rating item=song rating=song.rating setAction="updateRating"}} {{/each}}

E quindi usa quel nome per inviare l'azione:

 // app/components/star-rating.js export default Ember.Component.extend({ item: null, rating: 0, (...) actions: { set: function(newRating) { var item = this.get('item'); this.sendAction('setAction', { item: this.get('item'), rating: newRating }); } } });

Infine, l'azione viene gestita a monte, dal controllore o dal percorso, ed è qui che viene aggiornata la valutazione dell'oggetto:

 // app/routes/player.js export default Ember.Route.extend({ actions: { updateRating: function(params) { var skill = params.item, rating = params.rating; skill.set('score', rating); return skill.save(); } } });

Quando ciò accade, questa modifica viene propagata verso il basso attraverso il legame passato al componente star-rating e il numero di stelle complete visualizzate cambia di conseguenza.

In questo modo, la mutazione non si verifica nei componenti e poiché l'unica parte specifica dell'app è la gestione dell'azione nel percorso, la riutilizzabilità del componente non ne risente.

Potremmo anche usare lo stesso componente per le abilità calcistiche:

 {{#each player.skills as |skill|}} {{star-rating item=skill rating=skill.score setAction="updateSkill"}} {{/each}}

Parole finali

È importante notare che alcuni (la maggior parte?) degli errori che ho visto commettere (o commettere me stesso), compresi quelli di cui ho scritto qui, scompariranno o saranno notevolmente mitigati all'inizio della serie 2.x di Ember.js.

Ciò che rimane viene affrontato dai miei suggerimenti sopra, quindi una volta che stai sviluppando in Ember 2.x, non avrai più scuse per fare altri errori! Se desideri questo articolo in formato pdf, vai al mio blog e fai clic sul collegamento in fondo al post.

Riguardo a me

Sono arrivato nel mondo del front-end con Ember.js due anni fa e sono qui per restare. Sono diventato così entusiasta di Ember che ho iniziato a bloggare intensamente sia nei post degli ospiti che sul mio blog, oltre a presentare alle conferenze. Ho anche scritto un libro, Rock and Roll con Ember.js , per chiunque voglia imparare Ember. È possibile scaricare un capitolo di esempio qui.