Machen Sie Ihr Web-Frontend mit Elm zuverlässig

Veröffentlicht: 2022-03-11

Wie oft haben Sie versucht, Ihr Web-Front-End zu debuggen, und sich in Code verheddert, der sich mit komplexen Ereignisketten befasst?

Haben Sie jemals versucht, Code für eine Benutzeroberfläche umzugestalten, die sich mit vielen Komponenten befasst, die mit jQuery, Backbone.js oder anderen gängigen JavaScript-Frameworks erstellt wurden?

Eines der schmerzhaftesten Dinge an diesen Szenarien ist der Versuch, die zahlreichen unbestimmten Abfolgen von Ereignissen zu verfolgen und all diese Verhaltensweisen zu antizipieren und zu korrigieren. Einfach ein Alptraum!

Ich habe immer nach Möglichkeiten gesucht, diesem höllischen Aspekt der Web-Front-End-Entwicklung zu entkommen. Backbone.js hat für mich in dieser Hinsicht gut funktioniert, indem es Web-Frontends die Struktur gibt, die ihnen lange gefehlt hat. Aber angesichts der Ausführlichkeit, die es erfordert, um einige der trivialsten Dinge zu tun, stellte sich heraus, dass es nicht viel besser war.

Machen Sie Ihr Web-Frontend mit Elm zuverlässig

Dann traf ich Elm.

Elm ist eine statisch typisierte funktionale Sprache, die auf der Programmiersprache Haskell basiert, jedoch mit einer einfacheren Spezifikation. Der Compiler (ebenfalls mit Haskell erstellt) analysiert Elm-Code und kompiliert ihn in JavaScript.

Elm wurde ursprünglich für die Front-End-Entwicklung entwickelt, aber Softwareentwickler haben Wege gefunden, es auch für die serverseitige Programmierung zu verwenden.

Dieser Artikel gibt einen Überblick darüber, wie Elm unsere Vorstellung von Web-Front-End-Entwicklung verändern kann, und eine Einführung in die Grundlagen dieser funktionalen Programmiersprache. In diesem Tutorial entwickeln wir mit Elm eine einfache Warenkorb-ähnliche Anwendung.

Warum Ulme?

Elm verspricht viele Vorteile, von denen die meisten äußerst nützlich sind, um eine saubere Web-Frontend-Architektur zu erreichen. Elm bietet gegenüber anderen gängigen Frameworks (sogar React.js) eine bessere Leistung beim HTML-Rendering. Darüber hinaus ermöglicht Elm Entwicklern, Code zu schreiben, der in der Praxis die meisten Laufzeitausnahmen nicht erzeugt, die dynamisch typisierte Sprachen wie JavaScript plagen.

Der Compiler leitet Typen automatisch ab und gibt benutzerfreundliche Fehler aus, wodurch der Entwickler vor der Laufzeit auf potenzielle Probleme aufmerksam gemacht wird.

NoRedInk hat 36.000 Zeilen Elm und hat nach mehr als einem Jahr in der Produktion immer noch keine einzige Laufzeitausnahme produziert. [Quelle]

Sie müssen nicht Ihre gesamte vorhandene JavaScript-Anwendung konvertieren, nur um Elm auszuprobieren. Durch die hervorragende Interoperabilität mit JavaScript können Sie sogar nur einen kleinen Teil Ihrer bestehenden Anwendung nehmen und in Elm neu schreiben.

Elm verfügt auch über eine hervorragende Dokumentation, die Ihnen nicht nur eine gründliche Beschreibung dessen gibt, was es zu bieten hat, sondern Ihnen auch eine angemessene Anleitung zum Erstellen eines Web-Frontends gemäß der Elm-Architektur gibt - etwas, das sich hervorragend für Modularität, Wiederverwendung von Code und Tests eignet .

Lassen Sie uns einen einfachen Einkaufswagen machen

Beginnen wir mit einem sehr kurzen Ausschnitt aus Elm-Code:

 import List exposing (..) cart = [] item product quantity = { product = product, qty = quantity } product name price = { name = name, price = price } add cart product = if isEmpty (filter (\item -> item.product == product) cart) then append cart [item product 1] else cart subtotal cart = -- we want to calculate cart subtotal sum (map (\item -> item.product.price * toFloat item.qty) cart)

Jeder Text, dem -- vorangestellt ist, ist ein Kommentar in Elm.

Hier definieren wir einen Warenkorb als eine Liste von Artikeln, wobei jeder Artikel ein Datensatz mit zwei Werten ist (das Produkt, dem er entspricht, und die Menge). Jedes Produkt ist ein Datensatz mit einem Namen und einem Preis.

Beim Hinzufügen eines Produkts zum Warenkorb wird überprüft, ob der Artikel bereits im Warenkorb vorhanden ist.

Wenn ja, tun wir nichts; Andernfalls legen wir das Produkt als neuen Artikel in den Warenkorb. Wir prüfen, ob das Produkt bereits im Warenkorb vorhanden ist, indem wir die Liste filtern, jeden Artikel mit dem Produkt abgleichen und prüfen, ob die resultierende gefilterte Liste leer ist.

Um die Zwischensumme zu berechnen, durchlaufen wir die Artikel im Warenkorb, finden die entsprechende Produktmenge und den entsprechenden Preis und summieren alles.

Dies ist so minimalistisch wie ein Einkaufswagen und seine zugehörigen Funktionen nur sein können. Wir werden mit diesem Code beginnen und ihn Schritt für Schritt verbessern, um ihn zu einer vollständigen Webkomponente oder einem Programm in Elms Begriffen zu machen.

Beginnen wir damit, den verschiedenen Bezeichnern in unserem Programm Typen hinzuzufügen. Elm kann selbst Typen ableiten, aber um das Beste aus Elm und seinem Compiler herauszuholen, wird empfohlen, die Typen explizit anzugeben.

 module Cart1 exposing ( Cart, Item, Product , add, subtotal , itemSubtotal ) -- This is module and its API definition {-| We build an easy shopping cart. @docs Cart, Item, Product, add, subtotal, itemSubtotal -} import List exposing (..) -- we need list manipulation functions {-| Cart is a list of items. -} type alias Cart = List Item {-| Item is a record of product and quantity. -} type alias Item = { product : Product, qty : Int } {-| Product is a record with name and price -} type alias Product = { name : String, price : Float } {-| We want to add stuff to a cart. This is a function definition, it takes a cart, a product to add and returns new cart -} add : Cart -> Product -> Cart {-| This is an implementation of the 'add' function. Just append product item to the cart if there is no such product in the cart listed. Do nothing if the product exists. -} add cart product = if isEmpty (filter (\item -> item.product == product) cart) then append cart [Item product 1] else cart {-| I need to calculate cart subtotal. The function takes a cart and returns float. -} subtotal : Cart -> Float {-| It's easy -- just sum subtotal of all items. -} subtotal cart = sum (map itemSubtotal cart) {-| Item subtotal takes item and return the subtotal float. -} itemSubtotal : Item -> Float {-| Subtotal is based on product's price and quantity. -} itemSubtotal item = item.product.price * toFloat item.qty

Mit Typanmerkungen kann der Compiler jetzt Probleme abfangen, die andernfalls zu Laufzeitausnahmen geführt hätten.

Elm hört hier jedoch nicht auf. Die Elm-Architektur führt Entwickler durch ein einfaches Muster zur Strukturierung ihrer Web-Frontends, und zwar anhand von Konzepten, mit denen die meisten Entwickler bereits vertraut sind:

  • Modell: Modelle enthalten den Zustand des Programms.
  • View: View ist eine visuelle Darstellung des Zustands.
  • Update: Update ist eine Möglichkeit, den Status zu ändern.

Wenn Sie sich den Teil Ihres Codes, der sich mit Updates befasst, als Controller vorstellen, dann haben Sie etwas, das dem guten alten Model-View-Controller (MVC)-Paradigma sehr ähnlich ist.

Da es sich bei Elm um eine rein funktionale Programmiersprache handelt, sind alle Daten unveränderlich, was bedeutet, dass das Modell nicht verändert werden kann. Stattdessen können wir ein neues Modell basierend auf dem vorherigen erstellen, was wir durch Aktualisierungsfunktionen tun.

Warum ist das so toll?

Mit unveränderlichen Daten können Funktionen keine Seiteneffekte mehr haben. Dies eröffnet eine Welt voller Möglichkeiten, einschließlich des Zeitreise-Debuggers von Elm, auf den wir gleich eingehen werden.

Die Ansichten werden jedes Mal gerendert, wenn eine Änderung im Modell eine Änderung der Ansicht erfordert, und wir haben immer das gleiche Ergebnis für die gleichen Daten im Modell – ähnlich wie eine reine Funktion immer das gleiche Ergebnis für die zurückgibt gleiche Eingabeargumente.

Beginnen Sie mit der Hauptfunktion

Lassen Sie uns fortfahren und die HTML-Ansicht für unsere Einkaufswagenanwendung implementieren.

Wenn Sie mit React vertraut sind, werden Sie dies sicherlich zu schätzen wissen: Elm hat ein Paket nur zum Definieren von HTML-Elementen. Auf diese Weise können Sie Ihre Ansicht mit Elm implementieren, ohne sich auf externe Vorlagensprachen verlassen zu müssen.

Wrapper für die HTML-Elemente sind unter dem Html -Paket verfügbar:

 import Html exposing (Html, button, table, caption, thead, tbody, tfoot, tr, td, th, text, section, p, h1)

Alle Elm-Programme beginnen mit der Ausführung der main-Funktion:

 type alias Stock = List Product type alias Model = { cart : Cart, stock : Stock } main = Html.beginnerProgram { model = Model [] [ Product "Bicycle" 100.50 , Product "Rocket" 15.36 , Product "Biscuit" 21.15 ] , view = view , update = update }

Hier initialisiert die Hauptfunktion ein Elm-Programm mit einigen Modellen, einer Ansicht und einer Aktualisierungsfunktion. Wir haben einige Arten von Produkten und deren Preise definiert. Der Einfachheit halber gehen wir davon aus, dass wir eine unbegrenzte Anzahl von Produkten haben.

Eine einfache Update-Funktion

Die Update-Funktion erweckt unsere Anwendung zum Leben.

Es nimmt eine Nachricht entgegen und aktualisiert den Status basierend auf dem Inhalt der Nachricht. Wir definieren es als eine Funktion, die zwei Parameter (eine Nachricht und das aktuelle Modell) übernimmt und ein neues Modell zurückgibt:

 type Msg = Add Product update : Msg -> Model -> Model update msg model = case msg of Add product -> { model | cart = add model.cart product }

Im Moment behandeln wir einen einzelnen Fall, in dem die Nachricht Add product lautet, in dem wir die Methode add für cart mit dem product aufrufen.

Die Update-Funktion wächst mit der Komplexität des Elm-Programms.

Implementieren der View-Funktion

Als nächstes definieren wir die Ansicht für unseren Warenkorb.

Die Ansicht ist eine Funktion, die das Modell in seine HTML-Darstellung übersetzt. Es ist jedoch nicht nur statisches HTML. Der HTML-Generator kann basierend auf verschiedenen Benutzerinteraktionen und -ereignissen Nachrichten an die Anwendung zurücksenden.

 view : Model -> Html Msg view model = section [style [("margin", "10px")]] [ stockView model.stock , cartView model.cart ] stockView : Stock -> Html Msg stockView stock = table [] [ caption [] [ h1 [] [ text "Stock" ] ] , thead [] [ tr [] [ th [align "left", width 100] [ text "Name" ] , th [align "right", width 100] [ text "Price" ] , th [width 100] [] ] ] , tbody [] (map stockProductView stock) ] stockProductView : Product -> Html Msg stockProductView product = tr [] [ td [] [ text product.name ] , td [align "right"] [ text ("\t$" ++ toString product.price) ] , td [] [ button [ onClick (Add product) ] [ text "Add to Cart" ] ] ]

Das Html -Paket bietet Wrapper für alle häufig verwendeten Elemente als Funktionen mit vertrauten Namen (z. B. erzeugt der Funktionsabschnitt ein <section> section ).

Die style , Teil des Html.Attributes -Pakets, generiert ein Objekt, das an die section übergeben werden kann, um das Stilattribut für das resultierende Element festzulegen.

Es ist besser, die Ansicht für eine bessere Wiederverwendbarkeit in separate Funktionen aufzuteilen.

Um die Dinge einfach zu halten, haben wir CSS und einige Layoutattribute direkt in unseren Ansichtscode eingebettet. Es gibt jedoch Bibliotheken, die den Prozess des Stylens Ihrer HTML-Elemente aus Elm-Code rationalisieren.

Beachten Sie die button am Ende des Snippets und wie wir die Nachricht Add product mit dem Klickereignis der Schaltfläche verknüpft haben.

Elm kümmert sich um die Generierung des gesamten erforderlichen Codes zum Binden einer Callback-Funktion an das eigentliche Ereignis sowie um das Generieren und Aufrufen der Update-Funktion mit den relevanten Parametern.

Schließlich müssen wir den letzten Teil unserer Ansicht implementieren:

 cartView : Cart -> Html Msg cartView cart = if isEmpty cart then p [] [ text "Cart is empty" ] else table [] [ caption [] [ h1 [] [ text "Cart" ]] , thead [] [ tr [] [ th [ align "left", width 100 ] [ text "Name" ] , th [ align "right", width 100 ] [ text "Price" ] , th [ align "center", width 50 ] [ text "Qty" ] , th [ align "right", width 100 ] [ text "Subtotal" ] ] ] , tbody [] (map cartProductView cart) , tfoot [] [ tr [style [("font-weight", "bold")]] [ td [ align "right", colspan 4 ] [ text ("$" ++ toString (subtotal cart)) ] ] ] ] cartProductView : Item -> Html Msg cartProductView item = tr [] [ td [] [ text item.product.name ] , td [ align "right" ] [ text ("$" ++ toString item.product.price) ] , td [ align "center" ] [ text (toString item.qty) ] , td [ align "right" ] [ text ("$" ++ toString (itemSubtotal item)) ] ]

Hier haben wir den anderen Teil unserer Ansicht definiert, in dem wir den Inhalt unseres Warenkorbs darstellen. Obwohl die Ansichtsfunktion keine Nachricht ausgibt, muss sie dennoch den Rückgabetyp Html Msg haben, um sich als Ansicht zu qualifizieren.

Die Ansicht listet nicht nur den Inhalt des Einkaufswagens auf, sondern berechnet und rendert auch die Zwischensumme basierend auf dem Inhalt des Einkaufswagens.

Den vollständigen Code für dieses Elm-Programm finden Sie hier.

Wenn Sie das Elm-Programm jetzt ausführen würden, würden Sie Folgendes sehen:

Wie funktioniert das alles?

Unser Programm beginnt mit einem ziemlich leeren Zustand der main - einem leeren Warenkorb mit einigen fest codierten Produkten.

Bei jedem Klick auf den Button „In den Warenkorb“ wird eine Nachricht an die Aktualisierungsfunktion gesendet, die daraufhin den Warenkorb entsprechend aktualisiert und ein neues Modell erstellt. Immer wenn das Modell aktualisiert wird, werden die Ansichtsfunktionen von Elm aufgerufen, um den HTML-Baum neu zu generieren.

Da Elm einen Virtual DOM-Ansatz verwendet, ähnlich dem von React, werden Änderungen an der Benutzeroberfläche nur bei Bedarf durchgeführt, um eine schnelle Leistung zu gewährleisten.

Nicht nur ein Typprüfer

Elm ist statisch typisiert, aber der Compiler kann viel mehr als nur Typen prüfen.

Nehmen wir eine Änderung an unserem Msg -Typ vor und sehen, wie der Compiler darauf reagiert:

 type Msg = Add Product | ChangeQty Product String

Wir haben eine andere Art von Nachricht definiert – etwas, das die Menge eines Produkts im Warenkorb ändern würde. Wenn Sie jedoch versuchen, das Programm erneut auszuführen, ohne diese Nachricht in der Aktualisierungsfunktion zu behandeln, wird der folgende Fehler ausgegeben:

Auf dem Weg zu einem funktionaleren Wagen

Beachten Sie, dass wir im vorherigen Abschnitt eine Zeichenfolge als Typ für den Mengenwert verwendet haben. Dies liegt daran, dass der Wert von einem <input> -Element kommt, das vom Typ String ist.

Lassen Sie uns eine neue Funktion changeQty zum Cart -Modul hinzufügen. Es ist immer besser, die Implementierung innerhalb des Moduls zu belassen, um sie später bei Bedarf ändern zu können, ohne die Modul-API zu ändern.

 {-| Change quantity of the product in the cart. Look at the result of the function. It uses Result type. The Result type has two parameters: for bad and for good result. So the result will be Error "msg" or a Cart with updated product quantity. -} changeQty : Cart -> Product -> Int -> Result String Cart {-| If the quantity parameter is zero the product will be removed completely from the cart. If the quantity parameter is greater then zero the quantity of the product will be updated. Otherwise (qty < 0) the error will be returned. -} changeQty cart product qty = if qty == 0 then Ok (filter (\item -> item.product /= product) cart) else if qty > 0 then Result.Ok (map (\item -> if item.product == product then { item | qty = qty } else item) cart) else Result.Err ("Wrong negative quantity used: " ++ (toString qty))

Wir sollten keine Annahmen darüber treffen, wie die Funktion verwendet wird. Wir können sicher sein, dass der Parameter qty ein Int enthält, aber der Wert kann beliebig sein. Wir prüfen daher den Wert und melden einen Fehler, wenn er ungültig ist.

Auch unsere update Funktion aktualisieren wir entsprechend:

 update msg model = case msg of Add product -> { model | cart = add model.cart product } ChangeQty product str -> case toInt str of Ok qty -> case changeQty model.cart product qty of Ok cart -> { model | cart = cart } Err msg -> model -- do nothing, the wrong input Err msg -> model -- do nothing, the wrong quantity

Wir wandeln den String- quantity -Parameter aus der Nachricht in eine Zahl um, bevor wir ihn verwenden. Falls der String eine ungültige Zahl enthält, melden wir dies als Fehler.

Hier lassen wir das Modell unverändert, wenn ein Fehler auftritt. Alternativ könnten wir das Modell einfach so aktualisieren, dass der Fehler als Nachricht in der Ansicht gemeldet wird, die der Benutzer sehen kann:

 type alias Model = { cart : Cart, stock : Stock, error : Maybe String } main = Html.beginnerProgram { model = Model [] -- empty cart [ Product "Bicycle" 100.50 -- stock , Product "Rocket" 15.36 , Product "Bisquit" 21.15 ] Nothing -- error (no error at beginning) , view = view , update = update } update msg model = case msg of Add product -> { model | cart = add model.cart product } ChangeQty product str -> case toInt str of Ok qty -> case changeQty model.cart product qty of Ok cart -> { model | cart = cart, error = Nothing } Err msg -> { model | error = Just msg } Err msg -> { model | error = Just msg }

Wir verwenden in unserem Modell den Typ Maybe String für das Fehlerattribut. Vielleicht ist ein anderer Typ, der entweder Nothing oder einen Wert eines bestimmten Typs enthalten kann.

Nach dem Aktualisieren der Ansichtsfunktion wie folgt:

 view model = section [style [("margin", "10px")]] [ stockView model.stock , cartView model.cart , errorView model.error ] errorView : Maybe String -> Html Msg errorView error = case error of Just msg -> p [style [("color", "red")]] [ text msg ] Nothing -> p [] []

Sie sollten dies sehen:

Der Versuch, einen nicht numerischen Wert (z. B. „1a“) einzugeben, würde zu einer Fehlermeldung führen, wie im obigen Screenshot gezeigt.

Welt der Pakete

Elm hat ein eigenes Repository mit Open-Source-Paketen. Mit dem Paketmanager für Elm wird es zum Kinderspiel, diesen Pool an Paketen zu nutzen. Obwohl die Größe des Repositorys nicht mit einigen anderen ausgereiften Programmiersprachen wie Python oder PHP vergleichbar ist, arbeitet die Elm-Community hart daran, jeden Tag mehr Pakete zu implementieren.

Beachten Sie, wie die Dezimalstellen in den Preisen unserer Ansicht nach inkonsistent sind?

Ersetzen wir unsere naive Verwendung von toString durch etwas Besseres aus dem Repository: Zahlelm.

 cartProductView item = tr [] [ td [] [ text item.product.name ] , td [ align "right" ] [ text (formatPrice item.product.price) ] , td [ align "center" ] [ input [ value (toString item.qty) , onInput (ChangeQty item.product) , size 3 --, type' "number" ] [] ] , td [ align "right" ] [ text (formatPrice (itemSubtotal item)) ] ] formatPrice : Float -> String formatPrice price = format "$0,0.00" price

Wir verwenden hier die format aus dem Numeral-Paket. Dies würde die Zahlen so formatieren, wie wir normalerweise Währungen formatieren:

 100.5 -> $100.50 15.36 -> $15.36 21.15 -> $21.15

Automatische Dokumentationserstellung

Beim Veröffentlichen eines Pakets im Elm-Repository wird basierend auf den Kommentaren im Code automatisch eine Dokumentation generiert. Sie können es in Aktion sehen, indem Sie sich hier die Dokumentation für unser Cart-Modul ansehen. All dies wurde aus den Kommentaren generiert, die in dieser Datei zu sehen sind: Cart.elm.

Ein echter Debugger für Front-End

Die meisten offensichtlichen Probleme werden vom Compiler selbst erkannt und gemeldet. Allerdings ist keine Anwendung vor logischen Fehlern sicher.

Da alle Daten in Elm unveränderlich sind und alles durch Nachrichten geschieht, die an die Aktualisierungsfunktion übergeben werden, kann der gesamte Fluss eines Elm-Programms als eine Reihe von Modelländerungen dargestellt werden. Für den Debugger ist Elm wie ein rundenbasiertes Strategiespiel. Dies ermöglicht dem Debugger einige wirklich erstaunliche Leistungen, wie z. B. das Reisen durch die Zeit. Es kann sich durch den Fluss eines Programms hin und her bewegen, indem es zwischen verschiedenen Modelländerungen springt, die während der Lebensdauer eines Programms aufgetreten sind.

Hier erfahren Sie mehr über den Debugger.

Interaktion mit einem Back-End

Also, sagen Sie, wir haben ein nettes Spielzeug gebaut, aber kann Elm für etwas Ernstes verwendet werden? Absolut.

Verbinden wir unser Warenkorb-Front-End mit einem asynchronen Back-End. Um es interessant zu machen, werden wir etwas Besonderes implementieren. Angenommen, wir möchten alle Einkaufswagen und deren Inhalt in Echtzeit überprüfen. Im wirklichen Leben könnten wir diesen Ansatz verwenden, um einige zusätzliche Marketing-/Verkaufsfunktionen in unseren Online-Shop oder Marktplatz zu bringen, oder dem Benutzer einige Vorschläge zu machen, oder die erforderlichen Lagerressourcen zu schätzen, und vieles mehr.

Also speichern wir den Warenkorb auf der Client-Seite und informieren den Server auch in Echtzeit über jeden Warenkorb.

Um die Dinge einfach zu halten, werden wir unser Backend mit Python implementieren. Den vollständigen Code für das Backend finden Sie hier.

Es ist ein einfacher Webserver, der einen WebSocket verwendet und den Inhalt des Warenkorbs im Speicher verfolgt. Um die Dinge einfach zu halten, werden wir den Warenkorb aller anderen auf derselben Seite darstellen. Dies kann einfach in einer separaten Seite oder sogar als separates Elm-Programm implementiert werden. Im Moment kann jeder Benutzer die Zusammenfassung der Warenkörbe anderer Benutzer sehen.

Wenn das Back-End vorhanden ist, müssen wir jetzt unsere Elm-App aktualisieren, um Warenkorb-Updates an den Server zu senden und zu empfangen. Wir werden JSON verwenden, um unsere Payloads zu codieren, wofür Elm eine hervorragende Unterstützung bietet.

CartEncoder.elm

Wir werden einen Encoder implementieren, um unser Elm-Datenmodell in eine JSON-String-Darstellung zu konvertieren. Dafür müssen wir die Json.Encode-Bibliothek verwenden.

 module CartEncoder exposing (cart) import Cart2 exposing (Cart, Item, Product) import List exposing (map) import Json.Encode exposing (..) product : Product -> Value product product = object [ ("name", string product.name) , ("price", float product.price) ] item : Item -> Value item item = object [ ("product", product item.product) , ("qty", int item.qty) ] cart : Cart -> Value cart cart = list (map item cart)

Die Bibliothek bietet einige Funktionen (z. B. string , int , float , object usw.), die Elm-Objekte nehmen und sie in JSON-codierte Zeichenfolgen umwandeln.

CartDecoder.elm

Die Implementierung des Decoders ist etwas kniffliger, da alle Elm-Daten Typen haben und wir definieren müssen, welcher JSON-Wert in welchen Typ konvertiert werden muss:

 module CartDecoder exposing (cart) import Cart2 exposing (Cart, Item, Product) -- decoding for Cart import Json.Decode exposing (..) -- will decode cart from string cart : Decoder (Cart) cart = list item -- decoder for cart is a list of items item : Decoder (Item) item = object2 Item -- decoder for item is an object with two properties: ("product" := product) -- 1) "product" of product ("qty" := int) -- 2) "qty" of int product : Decoder (Product) product = object2 Product -- decoder for product also an object with two properties: ("name" := string) -- 1) "name" ("price" := float) -- 2) "price"

Elm-Anwendung aktualisiert

Da der endgültige Elm-Code etwas länger ist, finden Sie ihn hier. Hier ist eine Zusammenfassung der Änderungen, die an der Front-End-Anwendung vorgenommen wurden:

Wir haben unsere ursprüngliche update mit einer Funktion umhüllt, die Änderungen am Inhalt des Warenkorbs jedes Mal, wenn der Warenkorb aktualisiert wird, an das Backend sendet:

 updateOnServer msg model = let (newModel, have_to_send) = update msg model in case have_to_send of True -> -- send updated cart to server (!) newModel [ WebSocket.send server (encode 0 (CartEncoder.cart newModel.cart)) ] False -> -- do nothing (newModel, Cmd.none)

Wir haben auch einen zusätzlichen Nachrichtentyp von ConsumerCarts String hinzugefügt, um Updates vom Server zu erhalten und das lokale Modell entsprechend zu aktualisieren.

Die Ansicht wurde aktualisiert, um die Zusammenfassung der Warenkörbe anderer mithilfe der consumersCartsView -Funktion wiederzugeben.

Es wurde eine WebSocket-Verbindung hergestellt, um das Back-End zu abonnieren, um Änderungen an den Warenkörben anderer zu überwachen.

 subscriptions : Model -> Sub Msg subscriptions model = WebSocket.listen server ConsumerCarts server = "ws://127.0.0.1:8765"

Wir haben auch unsere Hauptfunktion aktualisiert. Wir verwenden jetzt Html.program mit zusätzlichen init und subscriptions -Parametern. init gibt das Anfangsmodell des Programms an und subscription gibt eine Liste von Abonnements an.

Ein Abonnement ist eine Möglichkeit für uns, Elm anzuweisen, auf Änderungen auf bestimmten Kanälen zu hören und diese Nachrichten an die update Funktion weiterzuleiten.

 main = Html.program { init = init , view = view , update = updateOnServer , subscriptions = subscriptions } init = ( Model [] -- empty cart [ Product "Bicycle" 100.50 -- stock , Product "Rocket" 15.36 , Product "Bisquit" 21.15 ] Nothing -- error (no error at beginning) [] -- consumer carts list is empty , Cmd.none)

Schließlich haben wir die Art und Weise behandelt, wie wir die ConsumerCarts-Nachricht entschlüsseln, die wir vom Server erhalten. Dadurch wird sichergestellt, dass Daten, die wir von externen Quellen erhalten, die Anwendung nicht beschädigen.

 ConsumerCarts message -> case decodeString (Json.Decode.list CartDecoder.cart) message of Ok carts -> ({ model | consumer_carts = carts }, False) Err msg -> ({ model | error = Just msg, consumer_carts = [] }, False)

Halten Sie Ihre Front-Ends sauber

Ulme ist anders. Es erfordert, dass der Entwickler anders denkt.

Jeder, der aus dem Bereich von JavaScript und ähnlichen Sprachen kommt, wird versuchen, Elms Vorgehensweise zu lernen.

Letztendlich bietet Elm jedoch etwas, womit andere Frameworks – selbst die beliebtesten – oft Schwierigkeiten haben. Es bietet nämlich die Möglichkeit, robuste Front-End-Anwendungen zu erstellen, ohne sich in riesigem, ausführlichem Code zu verheddern.

Elm abstrahiert auch viele der Schwierigkeiten, die JavaScript aufwirft, indem es einen intelligenten Compiler mit einem leistungsstarken Debugger kombiniert.

Elm ist das, wonach sich Frontend-Entwickler so lange gesehnt haben. Jetzt, da Sie es in Aktion gesehen haben, probieren Sie es selbst aus und profitieren Sie von den Vorteilen, indem Sie Ihr nächstes Webprojekt in Elm erstellen.