Elm Language
L'architettura di Elm
Ricerca…
introduzione
Il metodo consigliato per strutturare le tue applicazioni è soprannominato 'Elm Architecture.'
Il programma più semplice consiste in un record di model memorizza tutti i dati che potrebbero essere aggiornati, un Msg tipo union che definisce i modi in cui il programma aggiorna i dati, un update funzione che prende il modello e un Msg e restituisce un nuovo modello e una view funzione che prende un modello e restituisce il codice HTML che la pagina mostrerà. Ogni volta che una funzione restituisce un Msg , il runtime Elm lo utilizza per aggiornare la pagina.
Programma per principianti
Html ha beginnerProgram principalmente per scopi di apprendimento.
beginnerProgram non è in grado di gestire abbonamenti o comandi in esecuzione.
È solo in grado di gestire l'input dell'utente dagli eventi DOM.
Richiede solo una view per rendere il model e una funzione di update per gestire le modifiche di stato.
Esempio
Considera questo esempio minimale di beginnerProgram .
Il model in questo esempio è costituito da un singolo valore Int .
La funzione di update ha solo un ramo, che incrementa l' Int , memorizzato nel model .
La view esegue il rendering del modello e allega clic su Evento DOM.
Guarda come costruire l'esempio in Inizializza e crea
import Html exposing (Html, button, text)
import Html exposing (beginnerProgram)
import Html.Events exposing (onClick)
main : Program Never
main =
beginnerProgram { model = 0, view = view, update = update }
-- UPDATE
type Msg
= Increment
update : Msg -> Int -> Int
update msg model =
case msg of
Increment ->
model + 1
-- VIEW
view : Int -> Html Msg
view model =
button [ onClick Increment ] [ text ("Increment: " ++ (toString model)) ]
Programma
program è una buona scelta, quando l'applicazione non richiede dati esterni per l'inizializzazione.
È in grado di gestire abbonamenti e comandi, il che consente maggiori opportunità per la gestione degli I / O, come la comunicazione HTTP o l'interoperabilità con JavaScript.
Lo stato iniziale è necessario per restituire comandi di avvio insieme al modello.
L'inizializzazione del program richiederà la subscriptions di subscriptions , insieme al model , alla view e update .
Vedi la definizione del tipo:
program :
{ init : ( model, Cmd msg )
, update : msg -> model -> ( model, Cmd msg )
, subscriptions : model -> Sub msg
, view : model -> Html msg
}
-> Program Never
Esempio
Il modo più semplice per illustrare come utilizzare le Iscrizioni è impostare una semplice comunicazione Port con JavaScript.
Scopri come creare l'esempio in Inizializza e crea / Incorporamento in HTML
port module Main exposing (..)
import Html exposing (Html, text)
import Html exposing (program)
main : Program Never
main =
program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
port input : (Int -> msg) -> Sub msg
-- MODEL
type alias Model =
Int
init : ( Model, Cmd msg )
init =
( 0, Cmd.none )
-- UPDATE
type Msg = Incoming Int
update : Msg -> Model -> ( Model, Cmd msg )
update msg model =
case msg of
Incoming x ->
( x, Cmd.none )
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
input Incoming
-- VIEW
view : Model -> Html msg
view model =
text (toString model)
<!DOCTYPE html>
<html>
<head>
<script src='elm.js'></script>
</head>
<body>
<div id='app'></div>
<script>var app = Elm.Main.embed(document.getElementById('app'));</script>
<button onclick='app.ports.input.send(1);'>send</button>
</body>
</html>
Programma con le bandiere
programWithFlags ha una sola differenza dal program .
Può accettare i dati al momento dell'inizializzazione da JavaScript:
var root = document.body;
var user = { id: 1, name: "Bob" };
var app = Elm.Main.embed( root, user );
I dati, passati da JavaScript, sono chiamati Flags.
In questo esempio stiamo passando un oggetto JavaScript a Elm con le informazioni dell'utente, è buona norma specificare un alias di tipo per i flag.
type alias Flags =
{ id: Int
, name: String
}
Le flag vengono passate alla funzione init , producendo lo stato iniziale:
init : Flags -> ( Model, Cmd Msg )
init flags =
let
{ id, name } =
flags
in
( Model id name, Cmd.none )
Potresti notare la differenza dalla sua firma di tipo:
programWithFlags :
{ init : flags -> ( model, Cmd msg ) -- init now accepts flags
, update : msg -> model -> ( model, Cmd msg )
, subscriptions : model -> Sub msg
, view : model -> Html msg
}
-> Program flags
Il codice di inizializzazione sembra quasi lo stesso, dal momento che è solo la funzione init che è diversa.
main =
programWithFlags
{ init = init
, update = update
, view = view
, subscriptions = subscriptions
}
Comunicazione genitore-figlio a senso unico
L'esempio dimostra la composizione del componente e il messaggio unidirezionale che passa dal genitore ai figli.
La composizione del componente si basa sulla codifica dei messaggi con Html.App.map
In 0.18.0 HTML.App stato collassato in HTML
La composizione dei componenti si basa sulla codifica dei messaggi con Html.map
Esempio
Guarda come costruire l'esempio in Initialise e build
module Main exposing (..)
import Html exposing (text, div, button, Html)
import Html.Events exposing (onClick)
import Html.App exposing (beginnerProgram)
main =
beginnerProgram
{ view = view
, model = init
, update = update
}
{- In v0.18.0 HTML.App was collapsed into HTML
Use Html.map instead of Html.App.map
-}
view : Model -> Html Msg
view model =
div []
[ Html.App.map FirstCounterMsg (counterView model.firstCounter)
, Html.App.map SecondCounterMsg (counterView model.secondCounter)
, button [ onClick ResetAll ] [ text "Reset counters" ]
]
type alias Model =
{ firstCounter : CounterModel
, secondCounter : CounterModel
}
init : Model
init =
{ firstCounter = 0
, secondCounter = 0
}
type Msg
= FirstCounterMsg CounterMsg
| SecondCounterMsg CounterMsg
| ResetAll
update : Msg -> Model -> Model
update msg model =
case msg of
FirstCounterMsg childMsg ->
{ model | firstCounter = counterUpdate childMsg model.firstCounter }
SecondCounterMsg childMsg ->
{ model | secondCounter = counterUpdate childMsg model.secondCounter }
ResetAll ->
{ model
| firstCounter = counterUpdate Reset model.firstCounter
, secondCounter = counterUpdate Reset model.secondCounter
}
type alias CounterModel =
Int
counterView : CounterModel -> Html CounterMsg
counterView model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, text (toString model)
, button [ onClick Increment ] [ text "+" ]
]
type CounterMsg
= Increment
| Decrement
| Reset
counterUpdate : CounterMsg -> CounterModel -> CounterModel
counterUpdate msg model =
case msg of
Increment ->
model + 1
Decrement ->
model - 1
Reset ->
0
Tagging dei messaggi con Html.App.map
I componenti definiscono i propri messaggi, inviati dopo eventi DOM emessi, ad es. CounterMsg dalla comunicazione padre-figlio
type CounterMsg
= Increment
| Decrement
| Reset
La vista di questo componente invierà messaggi di tipo CounterMsg , pertanto la firma del tipo di vista è Html CounterMsg .
Per poter riutilizzare la vista del counterView all'interno della componente del genitore, dobbiamo passare ogni messaggio CounterMsg attraverso il Msg del genitore.
Questa tecnica è chiamata tagging dei messaggi .
Il componente principale deve definire i messaggi per il passaggio dei messaggi secondari:
type Msg
= FirstCounterMsg CounterMsg
| SecondCounterMsg CounterMsg
| ResetAll
FirstCounterMsg Increment è un messaggio con tag.
Per ottenere una counterView per inviare messaggi con tag, dobbiamo utilizzare la funzione Html.App.map :
Html.map FirstCounterMsg (counterView model.firstCounter)
Il pacchetto HTML.App è stato compresso nel pacchetto HTML in v0.18.0
Per ottenere una counterView per inviare messaggi con tag, dobbiamo utilizzare la funzione Html.map :
Html.map FirstCounterMsg (counterView model.firstCounter)
Ciò modifica la firma del tipo Html CounterMsg -> Html Msg modo che sia possibile utilizzare il contatore all'interno della vista genitore e gestire gli aggiornamenti di stato con la funzione di aggiornamento del genitore.