Faceți-vă web front-end fiabil cu Elm

Publicat: 2022-03-11

De câte ori ați încercat să vă depanați front-end-ul web și v-ați găsit încurcat în codul care se ocupă de lanțuri complexe de evenimente?

Ați încercat vreodată să refactorizați codul pentru o interfață de utilizare care se ocupă cu o mulțime de componente create cu jQuery, Backbone.js sau alte framework-uri JavaScript populare?

Unul dintre cele mai dureroase lucruri despre aceste scenarii este încercarea de a urmări multiplele secvențe nedeterminate de evenimente și anticiparea și remedierea tuturor acestor comportamente. Pur și simplu un coșmar!

Întotdeauna am căutat modalități de a scăpa de acest aspect infernal al dezvoltării web front-end. Backbone.js a funcționat bine pentru mine în acest sens, oferind front-end-urilor web structura care le lipsește de mult timp. Dar având în vedere verbozitatea pe care o cere pentru a face unele dintre cele mai banale lucruri, nu s-a dovedit a fi cu mult mai bine.

Faceți-vă web front-end fiabil cu Elm

Apoi l-am cunoscut pe Elm.

Elm este un limbaj funcțional tipizat static bazat pe limbajul de programare Haskell, dar cu o specificație mai simplă. Compilatorul (construit și folosind Haskell) analizează codul Elm și îl compilează în JavaScript.

Elm a fost construit inițial pentru dezvoltarea front-end, dar inginerii de software au găsit modalități de a-l folosi și pentru programarea pe partea de server.

Acest articol oferă o prezentare generală a modului în care Elm poate schimba modul în care gândim dezvoltarea web front-end și o introducere la elementele de bază ale acestui limbaj de programare funcțional. În acest tutorial, vom dezvolta o aplicație simplă asemănătoare coșului de cumpărături folosind Elm.

De ce Elm?

Elm promite o mulțime de avantaje, dintre care majoritatea sunt extrem de utile în realizarea unei arhitecturi web front-end curate. Elm oferă avantaje mai bune de performanță de redare HTML față de alte cadre populare (chiar și React.js). Mai mult decât atât, Elm permite dezvoltatorilor să scrie cod, care, în practică, nu produce majoritatea excepțiilor de rulare care afectează limbajele tastate dinamic, cum ar fi JavaScript.

Compilatorul deduce automat tipurile și emite erori prietenoase, făcându-l conștient de orice problemă potențială înainte de rulare.

NoRedInk are 36.000 de linii de Elm și, după mai mult de un an de producție, încă nu a produs o singură excepție de rulare. [Sursă]

Nu trebuie să convertiți întreaga aplicație JavaScript existentă doar pentru a putea încerca Elm. Prin interoperabilitatea sa superbă cu JavaScript, puteți chiar să luați doar o mică parte din aplicația dvs. existentă și să o rescrieți în Elm.

Elm are, de asemenea, o documentație excelentă, care nu numai că vă oferă o descriere amănunțită a ceea ce are de oferit, dar vă oferă și un ghid adecvat pentru construirea front-end-ului web după Arhitectura Elm - ceva excelent pentru modularitate, reutilizare a codului și testare. .

Să facem un cărucior simplu

Să începem cu un fragment foarte scurt de cod Elm:

 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)

Orice text precedat de -- este un comentariu în Elm.

Aici definim un coș ca o listă de articole, în care fiecare articol este o înregistrare cu două valori (produsul căruia îi corespunde și cantitatea). Fiecare produs este o înregistrare cu un nume și un preț.

Adăugarea unui produs în coș implică verificarea dacă articolul există deja în coș.

Dacă se întâmplă, nu facem nimic; în caz contrar, adăugăm produsul în coș ca articol nou. Verificăm dacă produsul există deja în coș prin filtrarea listei, potrivirea fiecărui articol cu ​​produsul și verificând dacă lista filtrată rezultată este goală.

Pentru a calcula subtotalul, repetăm ​​articolele din coș, găsim cantitatea și prețul produsului corespunzătoare și însumăm totul.

Acesta este la fel de minimalist precum un cărucior și funcțiile sale conexe. Vom începe cu acest cod și îl vom îmbunătăți pas cu pas făcându-l o componentă web completă sau un program în termenii lui Elm.

Să începem prin a adăuga tipuri la diferiții identificatori din programul nostru. Elm este capabil să deducă tipuri de la sine, dar pentru a profita la maximum de Elm și compilatorul său, se recomandă ca tipurile să fie indicate în mod explicit.

 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

Cu adnotări de tip, compilatorul poate detecta acum probleme care altfel ar fi dus la excepții de rulare.

Cu toate acestea, Elm nu se oprește aici. Arhitectura Elm ghidează dezvoltatorii printr-un model simplu pentru structurarea front-end-urilor lor web și face acest lucru prin concepte cu care majoritatea dezvoltatorilor sunt deja familiarizați:

  • Model: Modelele dețin starea programului.
  • Vedere: Vederea este o reprezentare vizuală a stării.
  • Actualizare: Actualizarea este o modalitate de a schimba starea.

Dacă vă gândiți la partea din codul dvs. care se ocupă de actualizări ca fiind controlerul, atunci aveți ceva foarte asemănător cu vechea paradigmă Model-View-Controller (MVC).

Deoarece Elm este un limbaj de programare pur funcțional, toate datele sunt imuabile, ceea ce înseamnă că modelul nu poate fi schimbat. În schimb, putem crea un nou model pe baza celui precedent, ceea ce facem prin funcții de actualizare.

De ce este atât de grozav?

Cu date imuabile, funcțiile nu mai pot avea efecte secundare. Acest lucru deschide o lume de posibilități, inclusiv depanatorul care călătorește în timp al lui Elm, despre care vom discuta în scurt timp.

Vizualizările sunt redate de fiecare dată când o modificare a modelului necesită o schimbare a vederii și vom avea întotdeauna același rezultat pentru aceleași date din model - în același mod în care o funcție pură returnează întotdeauna același rezultat pentru aceleași argumente de intrare.

Începeți cu funcția principală

Să mergem mai departe și să implementăm vizualizarea HTML pentru aplicația noastră de coș.

Dacă sunteți familiarizat cu React, acesta este ceva ce sunt sigur că veți aprecia: Elm are un pachet doar pentru definirea elementelor HTML. Acest lucru vă permite să vă implementați vizualizarea folosind Elm, fără a fi nevoie să vă bazați pe limbaje de șabloane externe.

Wrapper-urile pentru elementele HTML sunt disponibile sub pachetul Html :

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

Toate programele Elm încep prin a executa funcția principală:

 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 }

Aici, funcția principală inițializează un program Elm cu unele modele, o vizualizare și o funcție de actualizare. Am definit câteva tipuri de produse și prețurile acestora. Pentru simplitate, presupunem că avem un număr nelimitat de produse.

O funcție de actualizare simplă

Funcția de actualizare este locul unde aplicația noastră prinde viață.

Preia un mesaj și actualizează starea în funcție de conținutul mesajului. O definim ca o funcție care ia doi parametri (un mesaj și modelul curent) și returnează un nou model:

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

Deocamdată, ne ocupăm de un singur caz când mesajul este Add product , unde numim metoda de add în cart cu product .

Funcția de actualizare va crește odată cu creșterea complexității programului Elm.

Implementarea funcției View

În continuare, definim vederea pentru coșul nostru.

Vederea este o funcție care traduce modelul în reprezentarea sa HTML. Cu toate acestea, nu este doar HTML static. Generatorul HTML este capabil să emită mesaje înapoi către aplicație pe baza diferitelor interacțiuni și evenimente ale utilizatorului.

 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" ] ] ]

Pachetul Html furnizează wrapper-uri pentru toate elementele utilizate în mod obișnuit ca funcții cu nume familiare (de exemplu, section de funcție generează un element <section> ).

Funcția de style , parte a pachetului Html.Attributes , generează un obiect care poate fi transmis funcției de section pentru a seta atributul de stil pe elementul rezultat.

Este mai bine să împărțiți vizualizarea în funcții separate pentru o mai bună reutilizare.

Pentru a menține lucrurile simple, am încorporat CSS și unele atribute de aspect direct în codul nostru de vizualizare. Cu toate acestea, există biblioteci care simplifică procesul de stilare a elementelor HTML din codul Elm.

Observați button de lângă sfârșitul fragmentului și cum am asociat mesajul Add product evenimentului de clic al butonului.

Elm se ocupă de generarea întregului cod necesar pentru legarea unei funcții de apel invers cu evenimentul real și de generarea și apelarea funcției de actualizare cu parametri relevanți.

În cele din urmă, trebuie să implementăm ultimul fragment din viziunea noastră:

 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)) ] ]

Aici am definit cealaltă parte a vederii noastre în care redăm conținutul coșului nostru. Deși funcția de vizualizare nu emite niciun mesaj, totuși trebuie să aibă tipul de returnare Html Msg pentru a se califica ca vizualizare.

Vizualizarea nu numai că listează conținutul coșului, ci și calculează și redă subtotalul pe baza conținutului coșului.

Puteți găsi codul complet pentru acest program Elm aici.

Dacă ar fi să rulați programul Elm acum, ați vedea ceva de genul acesta:

Cum funcționează totul?

Programul nostru începe cu o stare destul de goală din funcția main - un coș gol cu ​​câteva produse codificate greu.

De fiecare dată când se face clic pe butonul „Adăugați în coș”, este trimis un mesaj către funcția de actualizare, care apoi actualizează coșul în consecință și creează un nou model. Ori de câte ori modelul este actualizat, funcțiile de vizualizare sunt invocate de Elm pentru a regenera arborele HTML.

Deoarece Elm folosește o abordare Virtual DOM, similară cu cea a React, modificările interfeței de utilizare sunt efectuate numai atunci când este necesar, asigurând performanțe rapide.

Nu doar un verificator de tip

Elm este scris static, dar compilatorul poate verifica mult mai mult decât doar tipuri.

Să facem o modificare a tipului nostru de Msg și să vedem cum reacționează compilatorul la asta:

 type Msg = Add Product | ChangeQty Product String

Am definit un alt fel de mesaj - ceva care ar schimba cantitatea unui produs din cos. Cu toate acestea, încercarea de a rula din nou programul fără a gestiona acest mesaj în funcția de actualizare va emite următoarea eroare:

Spre un coș mai funcțional

Rețineți că în secțiunea anterioară am folosit un șir ca tip pentru valoarea cantității. Acest lucru se datorează faptului că valoarea va veni de la un element <input> care va fi de tip șir.

Să adăugăm o nouă funcție changeQty la modulul Cart . Este întotdeauna mai bine să păstrați implementarea în interiorul modulului pentru a o putea schimba mai târziu, dacă este necesar, fără a schimba API-ul modulului.

 {-| 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))

Nu ar trebui să facem nicio presupunere cu privire la modul în care va fi utilizată funcția. Putem fi siguri că parametrul qty va conține un Int , dar valoarea poate fi orice. Prin urmare, verificăm valoarea și raportăm o eroare atunci când este invalidă.

De asemenea, ne update funcția de actualizare în consecință:

 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

Convertim parametrul de quantity șir din mesaj într-un număr înainte de a-l folosi. În cazul în care șirul conține un număr nevalid, îl raportăm ca o eroare.

Aici, păstrăm modelul neschimbat atunci când apare o eroare. Alternativ, am putea doar să actualizăm modelul astfel încât să raportăm eroarea ca mesaj în vizualizarea pentru ca utilizatorul să o vadă:

 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 }

Folosim tipul Maybe String pentru atributul de eroare din modelul nostru. Poate că este un alt tip care poate conține fie Nothing , fie o valoare de un anumit tip.

După actualizarea funcției de vizualizare, după cum urmează:

 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 [] []

Ar trebui să vezi asta:

Încercarea de a introduce o valoare non-numerică (de exemplu, „1a”) ar avea ca rezultat un mesaj de eroare, așa cum se arată în captura de ecran de mai sus.

Lumea pachetelor

Elm are propriul său depozit de pachete open source. Cu managerul de pachete pentru Elm, devine ușor să exploatezi acest grup de pachete. Deși dimensiunea depozitului nu este comparabilă cu alte limbaje de programare mature precum Python sau PHP, comunitatea Elm lucrează din greu pentru a implementa mai multe pachete în fiecare zi.

Observați cum zecimalele din prețuri redate în opinia noastră sunt inconsecvente?

Să înlocuim utilizarea naivă a toString cu ceva mai bun din depozit: numeral-elm.

 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

Aici folosim funcția de format din pachetul Numeral. Acest lucru ar formata numerele într-un mod în care de obicei formatăm monedele:

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

Generare automată a documentației

La publicarea unui pachet în depozitul Elm, documentația este generată automat pe baza comentariilor din cod. Îl puteți vedea în acțiune, verificând documentația pentru modulul nostru Coș aici. Toate acestea au fost generate din comentariile văzute în acest fișier: Cart.elm.

Un adevărat depanator pentru front-end

Cele mai evidente probleme sunt detectate și raportate chiar de compilator. Cu toate acestea, nicio aplicație nu este ferită de erori logice.

Deoarece toate datele din Elm sunt imuabile și totul se întâmplă prin mesaje transmise funcției de actualizare, întregul flux al unui program Elm poate fi reprezentat ca o serie de modificări de model. Pentru depanator, Elm este la fel ca un joc de strategie pe ture. Acest lucru permite depanatorului să efectueze câteva fapte cu adevărat uimitoare, cum ar fi călătoria în timp. Se poate deplasa înainte și înapoi prin fluxul unui program, sărind între diferitele modificări de model care au avut loc pe durata de viață a unui program.

Puteți afla mai multe despre depanator aici.

Interacțiunea cu un back-end

Deci, spuneți, am construit o jucărie drăguță, dar poate fi folosit Elm pentru ceva serios? Absolut.

Să conectăm front-end-ul căruciorului nostru cu un back-end asincron. Pentru a-l face interesant, vom implementa ceva special. Să presupunem că vrem să inspectăm toate cărucioarele și conținutul acestora în timp real. În viața reală, am putea folosi această abordare pentru a aduce unele capacități suplimentare de marketing/vânzări magazinului nostru online sau pieței noastre, sau pentru a face câteva sugestii utilizatorului sau pentru a estima resursele de stoc necesare și multe altele.

Deci, stocăm căruciorul pe partea clientului și, de asemenea, informăm serverul despre fiecare cărucior în timp real.

Pentru a menține lucrurile simple, vom implementa back-end-ul nostru folosind Python. Puteți găsi codul complet pentru back-end aici.

Este un server web simplu care folosește un WebSocket și ține evidența conținutului coșului în memorie. Pentru a menține lucrurile simple, vom afișa coșul tuturor celorlalți pe aceeași pagină. Acest lucru poate fi implementat cu ușurință într-o pagină separată sau chiar ca un program separat Elm. Deocamdată, fiecare utilizator va putea vedea rezumatul cărucioarelor altor utilizatori.

Cu back-end-ul instalat, acum va trebui să ne actualizăm aplicația Elm pentru a trimite și a primi actualizări de coș pe server. Vom folosi JSON pentru a codifica încărcăturile noastre utile, pentru care Elm are un suport excelent.

CartEncoder.elm

Vom implementa un encoder pentru a converti modelul nostru de date Elm într-o reprezentare de șir JSON. Pentru asta, trebuie să folosim biblioteca Json.Encode.

 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)

Biblioteca oferă unele funcții (cum ar fi string , int , float , object etc.) care preiau obiecte Elm și le transformă în șiruri de caractere codificate JSON.

CartDecoder.elm

Implementarea decodorului este puțin mai dificilă, deoarece toate datele Elm au tipuri și trebuie să definim ce valoare JSON trebuie convertită la ce tip:

 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"

Aplicația Elm actualizată

Deoarece codul final Elm este puțin mai lung, îl puteți găsi aici. Iată un rezumat al modificărilor care au fost aduse aplicației front-end:

Am împachetat funcția noastră de update originală cu o funcție care trimite modificări ale conținutului coșului către back-end de fiecare dată când coșul este actualizat:

 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)

De asemenea, am adăugat un tip de mesaj suplimentar ConsumerCarts String pentru a primi actualizări de la server și pentru a actualiza modelul local în consecință.

Vizualizarea a fost actualizată pentru a reda rezumatul cărucioarelor altora folosind funcția consumersCartsView .

A fost stabilită o conexiune WebSocket pentru a vă abona la back-end pentru a asculta modificările aduse cărucioarelor altora.

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

De asemenea, am actualizat funcția noastră principală. Acum folosim Html.program cu parametri suplimentari de init și subscriptions . init specifică modelul inițial al programului, iar subscription specifică o listă de abonamente.

Un abonament este o modalitate prin care îi spunem lui Elm să asculte modificările pe anumite canale și să trimită acele mesaje către funcția de update .

 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)

În cele din urmă, ne-am ocupat de modul în care decodăm mesajul ConsumerCarts pe care îl primim de la server. Acest lucru asigură că datele pe care le primim de la sursa externă nu vor rupe aplicația.

 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)

Păstrează-ți front-end-urile sănătoase

Elm este diferit. Necesită dezvoltatorului să gândească diferit.

Oricine provine din domeniul JavaScript și al limbilor similare se va găsi încercând să învețe felul lui Elm de a face lucrurile.

În cele din urmă, totuși, Elm oferă ceva ce alte cadre - chiar și cele mai populare - se chinuie adesea să facă. Și anume, oferă un mijloc de a construi aplicații front-end robuste, fără a fi încurcat în cod uriaș.

De asemenea, Elm abstrac multe dintre dificultățile pe care le prezintă JavaScript combinând un compilator inteligent cu un depanator puternic.

Elm este ceea ce dezvoltatorii front-end tânjesc de atâta timp. Acum că ați văzut-o în acțiune, faceți-o o învârtire și culegeți beneficiile creând următorul dvs. proiect web în Elm.