Comunicazione bambino-genitore in Elm: OutMsg vs Translator vs NoMap Patterns

Quando la tua app Elm inizia a crescere, vorrai dividerla in pezzi più piccoli per poterla ridimensionare. Ho trattato questo in un altro post sul blog: esempio di TodoMVC strutturato con Elm.

Parte se questo ridimensionamento alla fine implica la necessità di inviare un messaggio da un modulo al suo genitore, come quando i pulsanti di navigazione in una vista specifica devono inviare un messaggio al router di livello superiore.

Dopo un po 'di tempo ho iniziato a notare 3 diversi modelli per la gestione di questo, e ho refactored Elm TodoMVC a tutti quei diversi approcci in questo repository in modo da poterli confrontare fianco a fianco.

Il modello OutMsg

Credo che Folkertdev sia stato il primo che ho visto scrivere sulla comunicazione bambino-genitore in Elm, il suo post sul blog spiega abbastanza bene questo approccio.

Ma, per riassumere, sostanzialmente si restituisce un valore aggiuntivo nella funzione di aggiornamento. Quindi, invece di restituire questo:

(Modello, Cmd Msg)

Restituisci questo:

(Modello, Cmd Msg, OutMsg)

Quindi, la funzione di aggiornamento padre è responsabile della gestione di tali. In questo modo il bambino non ha bisogno di sapere nulla del suo genitore, ma il genitore deve conoscere le OutMsgs del figlio.

Ho implementato TodoMVC usando questo approccio. Ma se vuoi controllare una scala del mondo reale di questo, Richard Feldman ha implementato l'esempio di elm-spa in questo modo.

Un altro esempio che utilizza questo approccio è elm-datepicker.

Il modello del traduttore

Il modello di traduttore è molto simile a quello di OutMsg, ma invece che il genitore sia a conoscenza dei tipi di Msgs del bambino, è il genitore che passa il messaggio che verrà generato, tramite un traduttore. Alex Lew spiega il suo approccio molto meglio qui.

Fondamentalmente hai un traduttore che è un record come questo:

digitare alias TranslationDictionary msg =
  {onInternalMessage: InternalMsg -> msg
  , onPlayerWin: Int -> msg
  , onPlayerLose: msg
  }

Ho anche implementato TodoMVC usando questo approccio e credo che elm-autocomplete sia anche un buon esempio.

Elm-parent-child-update è una libreria che ti aiuta con l'aggiornamento child-parent che sembra seguire questo schema.

Il modello NoMap

Questo è qualcosa che ho notato che stavo facendo. L'idea di base è evitare di fare Cmd.map e Html.map, quindi invece tutti devono parlare la stessa lingua, in altre parole, le funzioni di aggiornamento e visualizzazione dovranno restituire il tipo Msg di livello superiore.

Con questo probabilmente avrai messaggi come MsgForLogin, MsgForRouter, ecc., Quindi nella tua vista faresti qualcosa del tipo:

pulsante [onClick (MsgForLogin SignUp)] []

È così che per la prima volta ho refactored TodoMVC, infatti, la prima volta che ho visto OutMsg non ho capito il motivo, perché non stavo mappando i miei messaggi.

Scopri l'app fulmine-talk per un esempio più ampio con questo approccio. Inoltre, questa app sembra seguire il modo di Kris Jenkins di strutturare le app Elm, il che favorisce questo approccio mentre separa i tipi Msgs in un file Types.elm.

La libreria elm-taco usa un mix di pattern OutMsg e NoMap avendo un "taco" di alto livello al quale puoi inviare messaggi.

Osservazioni e confronti

Durante la ricerca e il refactoring di questi modelli, ho notato qualcosa, che può essere vantaggi o svantaggi a seconda delle tue esigenze:

  • Su NoMap, la funzione di aggiornamento del genitore rimane pressoché uguale alla crescita della tua app, mentre su OutMsg e Translate la funzione di aggiornamento del genitore può diventare molto grande, poiché è necessario gestire l'OutMsg di ogni bambino (esempio)
  • Su OutMsg e Translate i moduli nidificati non devono importare nulla dai genitori superiori, rendendoli più incapsulati, sarebbe più semplice estrarre e pubblicare alcuni sottomoduli come una libreria, ad esempio
  • Perché NoMap funzioni, i tuoi messaggi dovrebbero risiedere in un file separato da Update, altrimenti avrai un ciclo di dipendenze. Questa è una buona causa perché ti costringe a dividere le cose, ma allo stesso tempo male se vuoi avere un singolo file per ogni modulo (Home.elm, Login.elm, Router.elm)
  • In NoMap, è più facile inviare messaggi da qualsiasi altra parte, ma potrebbe essere più difficile seguire tutti i cambiamenti di stato causati da esso.
  • Come misurato al momento della stesura di questo scritto, per i refattori TodoMVC, l'approccio NoMap ha 546 LOC, OutMsg 561 e Translator 612 se questo è importante per te
  • Su NoMap alla fine è necessario utilizzare il caso _ catch-all per ignorare i messaggi da altri luoghi che non si desidera gestire, quindi c'è meno aiuto dal compilatore, non si può dire cosa ci si perde (grazie per @mordrax per il puntamento che su olmo molle)
  • In OutMsg e Translator puoi semplicemente guardare i tipi o i traduttori per scoprire quali comunicazioni figlio-genitore sono necessarie, quindi il compilatore può guidarti a implementarle, mentre su NoMap questa comunicazione è più implicita
  • L'approccio del traduttore sembra essere una buona idea per dare i propri messaggi a un componente esterno, come elm-autocomplete
  • Ho trovato il modello del traduttore difficile da seguire con i messaggi di errore più difficili da comprendere dal compilatore Elm mentre lo costruivo
  • Se non modifichi lo standard (Model, Cmd Msg) puoi usare la libreria fine elm-return
  • Alcune persone considerano non avere Html.map come una buona pratica per evitare di creare "componenti"
  • Puoi trarre molti benefici dalla miscelazione di questi approcci, ad esempio, potresti semplicemente evitare Html.map per le viste, mentre usi ancora OutMsg per gli aggiornamenti o potresti usare NoMap solo per i messaggi di livello superiore, con OutMsgs in basso, durante il rendering di un componente tradotto esterno

risorse

Credo che la comunicazione bambino-genitore sia spesso più importante quando si ridimensionano e si eseguono SPA, ecco perché molte cose che ho scoperto stavano leggendo questo thread reddit su Scaling Elm Apps:

Saluti!