Învățarea ulmului dintr-un secvențător de tobe (Partea 1)
Publicat: 2022-03-10Dacă sunteți un dezvoltator front-end care urmărește evoluția aplicațiilor cu o singură pagină (SPA), este probabil că ați auzit de Elm, limbajul funcțional care a inspirat Redux. Dacă nu ați făcut-o, este un limbaj de compilare în JavaScript comparabil cu proiectele SPA precum React, Angular și Vue.
Asemenea acelor, gestionează schimbările de stare prin domul său virtual, urmărind să facă codul mai ușor de întreținut și mai performant. Se concentrează pe fericirea dezvoltatorului, instrumente de înaltă calitate și modele simple și repetabile. Unele dintre diferențele sale cheie includ faptul că sunt tipărite static, mesaje de eroare extraordinar de utile și că este un limbaj funcțional (spre deosebire de orientat pe obiecte).
Introducerea mea a venit printr-o discuție susținută de Evan Czaplicki, creatorul lui Elm, despre viziunea sa pentru experiența dezvoltatorului front-end și, la rândul său, viziunea pentru Elm. Deoarece cineva s-a concentrat și pe mentenabilitatea și utilizarea dezvoltării front-end, discursul lui a rezonat cu adevărat cu mine. Am încercat Elm într-un proiect secundar în urmă cu un an și continuă să mă bucur de atât caracteristicile, cât și provocările sale într-un mod pe care nu l-am mai făcut de când am început să programez; Sunt din nou începător. În plus, sunt capabil să aplic multe dintre practicile lui Elm în alte limbi.
Dezvoltarea conștientizării dependenței
Dependențe sunt peste tot. Reducendu-le, puteți îmbunătăți probabilitatea ca site-ul dvs. să fie utilizat de cel mai mare număr de persoane în cea mai mare varietate de scenarii. Citiți un articol similar →
În acest articol din două părți, vom construi un secvențietor în trepte pentru a programa ritmurile de tobe în Elm, prezentând în același timp unele dintre cele mai bune caracteristici ale limbajului. Astăzi, vom parcurge conceptele fundamentale din Elm, adică începerea, utilizarea tipurilor, redarea vizualizărilor și actualizarea stării. A doua parte a acestui articol va aborda apoi subiecte mai avansate, cum ar fi gestionarea cu ușurință a refactorilor mari, configurarea evenimentelor recurente și interacțiunea cu JavaScript.
Joacă-te cu proiectul final aici și verifică codul acestuia aici.
Începeți cu Elm
Pentru a urmări acest articol, vă recomand să utilizați Ellie, o experiență de dezvoltator Elm în browser. Nu trebuie să instalați nimic pentru a rula Ellie și puteți dezvolta aplicații complet funcționale în ea. Dacă preferați să instalați Elm pe computer, cel mai bun mod de a vă configura este să urmați ghidul oficial de pornire.
De-a lungul acestui articol, voi crea un link către versiunile Ellie în lucru, deși am dezvoltat secvențatorul local. Și în timp ce CSS poate fi scris în întregime în Elm, am scris acest proiect în PostCSS. Acest lucru necesită puțină configurare la Elm Reactor pentru dezvoltare locală pentru a avea stiluri încărcate. De dragul conciziei, nu voi atinge stilurile în acest articol, dar linkurile Ellie includ toate stilurile CSS reduse.
Elm este un ecosistem autonom care include:
- Elm Make
Pentru compilarea codului dvs. Elm. Deși Webpack este încă popular pentru producția de proiecte Elm alături de alte active, nu este necesar. În acest proiect, am ales să exclud Webpack și să mă bazez peelm make
pentru a compila codul. - Pachet Elm
Un manager de pachete comparabil cu NPM pentru utilizarea pachetelor/modulelor create de comunitate. - Reactorul Elm
Pentru rularea unui server de dezvoltare cu compilare automată. Mai notabil, include Time Traveling Debugger, ceea ce face ușoară trecerea prin stările aplicației și erorile de reluare . - Elm Repl
Pentru scrierea sau testarea expresiilor simple Elm în terminal.
Toate fișierele Elm sunt considerate modules
. Liniile de început ale oricărui fișier vor include module FileName exposing (functions)
unde FileName
este numele literal al fișierului, iar functions
sunt funcțiile publice pe care doriți să le faceți accesibile altor module. Imediat după definirea modulului sunt importuri de la module externe. Urmează restul funcțiilor.
module Main exposing (main) import Html exposing (Html, text) main : Html msg main = text "Hello, World!"
Acest modul, numit Main.elm
, expune o singură funcție, main
, și importă Html
și text
din modulul/pachetul Html
. Funcția main
este formată din două părți: adnotarea tipului și funcția reală. Adnotările de tip pot fi considerate definiții de funcție. Ele indică tipurile de argument și tipul de returnare. În acest caz, a noastră afirmă că funcția main
nu acceptă argumente și returnează Html msg
. Funcția în sine redă un nod text care conține „Hello, World”. Pentru a transmite argumente unei funcții, adăugăm nume separate prin spațiu înainte de semnul egal în funcție. Adăugăm și tipurile de argument la adnotarea tipului, în ordinea argumentelor, urmate de o săgeată.
add2Numbers : Int -> Int -> Int add2Numbers first second = first + second
În JavaScript, o funcție ca aceasta este comparabilă:
function add2Numbers(first, second) { return first + second; }
Și într-un limbaj Tastat, cum ar fi TypeScript, arată astfel:
function add2Numbers(first: number, second: number): number { return first + second; }
add2Numbers
ia două numere întregi și returnează un număr întreg. Ultima valoare din adnotare este întotdeauna valoarea returnată, deoarece fiecare funcție trebuie să returneze o valoare. Numim add2Numbers
cu 2 și 3 pentru a obține 5 ca add2Numbers 2 3
.
La fel cum legați componentele React, trebuie să legăm codul Elm compilat la DOM. Modul standard de a lega este să apelați embed()
pe modulul nostru și să treceți elementul DOM în el.
<script> const container = document.getElementById('app'); const app = Elm.Main.embed(container); <script>
Deși aplicația noastră nu face nimic, avem suficient pentru a compila codul nostru Elm și a reda textul. Verificați-l pe Ellie și încercați să schimbați argumentele în add2Numbers
pe linia 26.
Modelarea datelor cu tipuri
Venind dintr-un limbaj tip dinamic precum JavaScript sau Ruby, tipurile pot părea de prisos. Aceste limbi determină ce tip de funcții iau din valoarea transmisă în timpul rulării. Funcțiile de scriere sunt, în general, considerate mai rapide, dar pierdeți siguranța de a vă asigura că funcțiile dvs. pot interacționa corect între ele.
În schimb, Elm este tipizat static. Se bazează pe compilatorul său pentru a se asigura că valorile transmise funcțiilor sunt compatibile înainte de timpul de rulare. Aceasta înseamnă că nu există excepții de rulare pentru utilizatorii dvs. și este modul în care Elm își poate garanta „fără excepții de rulare”. Acolo unde erorile de tip din multe compilatoare pot fi deosebit de criptice, Elm se concentrează pe a le face ușor de înțeles și corectat.
Elm face începutul cu tipurile foarte prietenos. De fapt, inferența de tip a lui Elm este atât de bună încât poți sări peste adnotările scrise până când te simți mai confortabil cu ele. Dacă sunteți nou în ceea ce privește tipurile, vă recomand să vă bazați pe sugestiile compilatorului, mai degrabă decât să încercați să le scrieți singur.
Să începem să modelăm datele noastre folosind tipuri. Sequencerul nostru pas este o cronologie vizuală a momentului în care ar trebui să cânte o anumită probă de tobe. Linia temporală constă din piese , fiecare alocată cu un eșantion specific de tobă și secvența de pași . Un pas poate fi considerat un moment în timp sau o bătaie. Dacă un pas este activ , eșantionul ar trebui să fie declanșat în timpul redării, iar dacă pasul este inactiv , eșantionul ar trebui să rămână tăcut. În timpul redării, secvențatorul se va deplasa prin fiecare pas redând mostrele pașilor activi. Viteza de redare este setată de Beats Per Minute (BPM) .
Modelarea aplicației noastre în JavaScript
Pentru a ne face o idee mai bună despre tipurile noastre, să ne gândim cum să modelăm acest secvențior de tobe în JavaScript. Există o serie de piese. Fiecare obiect track conține informații despre sine: numele piesei, proba/clipul care va declanșa și secvența valorilor pasului.
tracks: [ { name: "Kick", clip: "kick.mp3", sequence: [On, Off, Off, Off, On, etc...] }, { name: "Snare", clip: "snare.mp3", sequence: [Off, Off, Off, Off, On, etc...] }, etc... ]
Trebuie să gestionăm starea de redare între redare și oprire.
playback: "playing" || "stopped"
În timpul redării, trebuie să stabilim ce pas ar trebui să fie redat. De asemenea, ar trebui să luăm în considerare performanța de redare și, mai degrabă, decât să parcurgem fiecare secvență din fiecare piesă de fiecare dată când un pas este incrementat; ar trebui să reducem toți pașii activi într-o singură secvență de redare. Fiecare colecție din secvența de redare reprezintă toate mostrele care ar trebui redate. De exemplu, ["kick", "hat"]
înseamnă că probele de lovitură și hi-hat ar trebui să se joace, în timp ce ["hat"]
înseamnă doar hi-hat-ul ar trebui să joace. De asemenea, avem nevoie de fiecare colecție pentru a limita unicitatea eșantionului, astfel încât să nu ajungem la ceva de genul ["hat", "hat", "hat"]
.
playbackPosition: 1 playbackSequence: [ ["kick", "hat"], [], ["hat"], [], ["snare", "hat"], [], ["hat"], [], ... ],
Și trebuie să setăm ritmul de redare sau BPM-ul.
bpm: 120
Modelare Cu Tipuri În Elm
Transcrierea acestor date în tipuri Elm este, în esență, să descrie din ce ne așteptăm să fie făcute datele noastre. De exemplu, ne referim deja la modelul nostru de date ca model , așa că îl numim așa cu un alias de tip. Aliasurile de tip sunt folosite pentru a face codul mai ușor de citit. Nu sunt un tip primitiv ca un boolean sau un întreg; sunt pur și simplu nume pe care le dăm un tip primitiv sau o structură de date. Folosind unul, definim orice date care urmează structura modelului nostru ca un model , mai degrabă decât ca o structură anonimă. În multe proiecte Elm, structura principală este numită Model.
type alias Model = { tracks : Array Track , playback : Playback , playbackPosition : PlaybackPosition , bpm : Int , playbackSequence : Array (Set Clip) }
Deși modelul nostru arată un pic ca un obiect JavaScript, descrie o înregistrare Elm. Înregistrările sunt folosite pentru a organiza datele conexe în mai multe câmpuri care au propriile adnotări de tip. Sunt ușor de accesat folosind field.attribute
și ușor de actualizat, ceea ce vom vedea mai târziu. Obiectele și înregistrările sunt foarte asemănătoare, cu câteva diferențe cheie:
- Câmpurile inexistente nu pot fi apelate
- Câmpurile nu vor fi niciodată
null
sauundefined
-
this
șiself
nu pot fi folosite
Colecția noastră de melodii poate fi formată dintr-unul din trei tipuri posibile: Liste, Matrice și Seturi. Pe scurt, Listele sunt colecții de utilizare generală neindexate, Array-urile sunt indexate, iar Seturile conțin numai valori unice. Avem nevoie de un index pentru a ști ce pas de pistă a fost comutat și, deoarece tablourile sunt indexate, este cea mai bună alegere a noastră. Alternativ, am putea adăuga un id la pistă și să filtram dintr-o listă.
În modelul nostru, am format piese într-o matrice de track , o altă înregistrare: tracks : Array Track
. Track conține informații despre sine. Atât numele, cât și clipul sunt șiruri de caractere, dar am tastat un clip cu alias, deoarece știm că va fi referit în altă parte în cod de către alte funcții. Prin alias, începem să creăm cod de auto-documentare. Crearea de tipuri și aliasuri de tip permite dezvoltatorilor să modeleze modelul de date după modelul de afaceri, creând un limbaj omniprezent.
type alias Track = { name : String , clip : Clip , sequence : Array Step } type Step = On | Off type alias Clip = String
Știm că secvența va fi o matrice de valori pornit/oprit. L-am putea seta ca o matrice de booleeni, cum ar fi sequence : Array Bool
, dar am rata o oportunitate de a ne exprima modelul de afaceri! Având în vedere că secvențialele de pași sunt formate din pași , definim un nou tip numit Step . Un Step ar putea fi un alias de tip pentru un boolean
, dar putem merge mai departe: Steps au două valori posibile, on și off, așa că definim tipul de unire. Acum pașii pot fi doar porniți sau opriți, făcând imposibile toate celelalte stări.
Definim un alt tip pentru Playback
, un alias pentru PlaybackPosition
și folosim Clip când definim playbackSequence
ca un Array care conține seturi de Clipuri. BPM este atribuit ca un standard Int
.
type Playback = Playing | Stopped type alias PlaybackPosition = Int
În timp ce începerea cu tipurile este puțin mai mare, codul nostru este mult mai ușor de întreținut. Este auto-documentat și folosește un limbaj omniprezent cu modelul nostru de afaceri. Încrederea pe care o câștigăm în a cunoaște funcțiile noastre viitoare vor interacționa cu datele noastre într-un mod în care ne așteptăm, fără a necesita teste, merită din plin timpul necesar pentru a scrie o adnotare. Și, ne-am putea baza pe inferența de tip a compilatorului pentru a sugera tipurile, astfel încât să le scriem să fie la fel de simplă precum copierea și lipirea. Iată declarația completă de tip.
Folosind Arhitectura Elm
Arhitectura Elm este un model simplu de management de stat care a apărut în mod natural în limbaj. Se concentrează în jurul modelului de afaceri și este foarte scalabil. Spre deosebire de alte framework-uri SPA, Elm are opinie cu privire la arhitectura sa - este modul în care sunt structurate toate aplicațiile, ceea ce face ca integrarea să fie o briză. Arhitectura este formată din trei părți:
- Modelul , care conține starea aplicației și structura pe care o introducem modelul alias
- Funcția de actualizare , care actualizează starea
- Și funcția de vizualizare , care redă starea vizual
Să începem să construim secvențiarul nostru de tobe, învățând Arhitectura Elm în practică pe măsură ce mergem. Vom începe prin inițializarea aplicației noastre, redarea vizualizării, apoi actualizarea stării aplicației. Venind dintr-un fundal Ruby, tind să prefer fișierele mai scurte și să-mi împart funcțiile Elm în module, deși este foarte normal să am fișiere Elm mari. Am creat un punct de plecare pe Ellie, dar la nivel local am creat următoarele fișiere:
- Types.elm, care conține toate definițiile tipului
- Main.elm, care inițializează și rulează programul
- Update.elm, care conține funcția de actualizare care gestionează starea
- View.elm, care conține codul Elm pentru redare în HTML
Inițializarea aplicației noastre
Cel mai bine este să începem mic, așa că reducem modelul pentru a se concentra pe construirea unei singure piese care să conțină pași care se activează și dezactivează. Deși credem deja că cunoaștem întreaga structură de date, începerea mic ne permite să ne concentrăm asupra redării pistelor ca HTML. Reduce complexitatea și codul You Ain't Gonna Need It. Mai târziu, compilatorul ne va ghida prin refactorizarea modelului nostru. În fișierul Types.elm, păstrăm tipurile Pas și Clip, dar schimbăm modelul și pista.
type alias Model = { track : Track } type alias Track = { name : String , sequence : Array Step } type Step = On | Off type alias Clip = String
Pentru a reda Elm ca HTML, folosim pachetul Elm Html. Are opțiuni pentru a crea trei tipuri de programe care se bazează unul pe celălalt:
- Program pentru începători
Un program redus care exclude efectele secundare și este util în special pentru învățarea Arhitecturii Elm. - Program
Programul standard care gestionează efectele secundare, util pentru a lucra cu baze de date sau instrumente care există în afara Elm. - Program cu steaguri
Un program extins care se poate inițializa cu date reale în loc de date implicite.
Este o practică bună să utilizați cel mai simplu tip de program posibil, deoarece este ușor să îl schimbați ulterior cu compilatorul. Aceasta este o practică comună atunci când programați în Elm; utilizați doar ceea ce aveți nevoie și schimbați-l mai târziu. Pentru scopurile noastre, știm că trebuie să ne ocupăm de JavaScript, care este considerat un efect secundar, așa că creăm un Html.program
. În Main.elm trebuie să inițializam programul pasând funcții în câmpurile sale.
main : Program Never Model Msg main = Html.program { init = init , view = view , update = update , subscriptions = always Sub.none }
Fiecare câmp din program transmite o funcție la Elm Runtime, care controlează aplicația noastră. Pe scurt, Elm Runtime:
- Pornește programul cu valorile noastre inițiale din
init
. - Redă prima vedere prin trecerea modelului nostru inițializat în
view
. - Redă în mod continuu vizualizarea atunci când mesajele sunt transmise pentru a fi
update
din vizualizări, comenzi sau abonamente.
La nivel local, funcțiile noastre de view
și update
vor fi importate din View.elm
și, respectiv, Update.elm
și le vom crea într-un moment. subscriptions
ascultă mesajele care provoacă actualizări, dar deocamdată, le ignorăm, atribuind always Sub.none
. Prima noastră funcție, init
, inițializează modelul. Gândiți-vă la init
ca la valorile implicite pentru prima încărcare. Îl definim cu o singură piesă numită „kick” și o secvență de pași Off. Deoarece nu primim date asincrone, ignorăm în mod explicit comenzile cu Cmd.none
pentru a le inițializa fără efecte secundare.
init : ( Model, Cmd.Cmd Msg ) init = ( { track = { sequence = Array.initialize 16 (always Off) , name = "Kick" } } , Cmd.none )
Adnotarea noastră de tip init se potrivește cu programul nostru. Este o structură de date numită tuplu, care conține un număr fix de valori. În cazul nostru, Model
și comenzile. Pentru moment, ignorăm întotdeauna comenzile utilizând Cmd.none
până când suntem gata să gestionăm efectele secundare mai târziu. Aplicația noastră nu redă nimic, dar se compilează!
Redarea aplicației noastre
Să ne construim opiniile. În acest moment, modelul nostru are o singură pistă, așa că acesta este singurul lucru pe care trebuie să îl redăm. Structura HTML ar trebui să arate astfel:
<div class="track"> <p class "track-title">Kick</p> <div class="track-sequence"> <button class="step _active"></button> <button class="step"></button> <button class="step"></button> <button class="step"></button> etc... </div> </div>
Vom construi trei funcții pentru a ne reda vederile:
- Unul pentru a reda o singură piesă, care conține numele și secvența piesei
- Altul pentru a reda secvența în sine
- Și încă unul pentru a reda fiecare buton de pas individual din secvență
Prima noastră funcție de vizualizare va reda o singură pistă. Ne bazăm pe adnotarea noastră de tip, renderTrack : Track -> Html Msg
, pentru a impune o singură pistă trecută. Folosirea tipurilor înseamnă că știm întotdeauna că renderTrack
va avea o pistă. Nu trebuie să verificăm dacă câmpul de name
există în înregistrare sau dacă am trecut un șir în loc de o înregistrare. Elm nu va compila dacă încercăm să transmitem altceva decât Track
la renderTrack
. Și mai bine, dacă facem o greșeală și încercăm din greșeală să transmitem funcției altceva decât o pistă, compilatorul ne va oferi mesaje prietenoase care să ne îndrepte în direcția corectă.
renderTrack : Track -> Html Msg renderTrack track = div [ class "track" ] [ p [ class "track-title" ] [ text track.name ] , div [ class "track-sequence" ] (renderSequence track.sequence) ]
Ar putea părea evident, dar tot Elm este Elm, inclusiv scrierea HTML. Nu există un limbaj de șabloane sau o abstracție pentru a scrie HTML - totul este Elm. Elementele HTML sunt funcții Elm, care preiau numele, o listă de atribute și o listă de copii. Deci div [ class "track" ] []
produce <div class="track"></div>
. Listele sunt separate prin virgulă în Elm, așa că adăugarea unui id la div ar arăta ca div [ class "track", id "my-id" ] []
.
Secvența track-sequence
piesei la cea de-a doua funcție a noastră, renderSequence
. Este nevoie de o secvență și returnează o listă de butoane HTML. Am putea păstra renderSequence
în renderTrack
pentru a sări peste funcția suplimentară, dar mi se pare mult mai ușor de gândit că împărțirea funcțiilor în bucăți mai mici. În plus, avem o altă oportunitate de a defini o adnotare de tip mai strictă.
renderSequence : Array Step -> List (Html Msg) renderSequence sequence = Array.indexedMap renderStep sequence |> Array.toList
Mapăm fiecare pas din secvență și îl transmitem în funcția renderStep
. În JavaScript, maparea cu un index ar fi scrisă astfel:
sequence.map((node, index) => renderStep(index, node))
În comparație cu JavaScript, maparea în Elm este aproape inversată. Numim Array.indexedMap
, care ia două argumente: funcția care trebuie aplicată în hartă ( renderStep
) și matricea de mapat peste ( sequence
). renderStep
este ultima noastră funcție și determină dacă un buton este activ sau inactiv. Folosim indexedMap
deoarece trebuie să transmitem indexul pasului (pe care îl folosim ca ID) pasului în sine pentru a-l transmite funcției de actualizare.
renderStep : Int -> Step -> Html Msg renderStep index step = let classes = if step == On then "step _active" else "step" in button [ class classes ] []
renderStep
acceptă indexul ca prim argument, pasul ca al doilea și returnează HTML redat. Folosind un bloc let...in
pentru a defini funcțiile locale, atribuim clasa _active
la On Steps și apelăm funcția claselor noastre în lista de atribute de buton.
Se actualizează starea aplicației
În acest moment, aplicația noastră redă cei 16 pași din secvența de lovire, dar făcând clic nu activează pasul. Pentru a actualiza starea pasului, trebuie să transmitem un mesaj ( Msg
) la funcția de actualizare. Facem acest lucru definind un mesaj și atașându-l la un handler de evenimente pentru butonul nostru.
În Types.elm, trebuie să definim primul nostru mesaj, ToggleStep
. Va fi nevoie de un Int
pentru indexul secvenței și un Step
. Apoi, în renderStep
, atașăm mesajul ToggleStep
la evenimentul butonului la clic, împreună cu indexul de secvență și pasul ca argumente. Acest lucru va trimite mesajul către funcția noastră de actualizare, dar în acest moment, actualizarea nu va face nimic.
type Msg = ToggleStep Int Step renderStep index step = let ... in button [ onClick (ToggleStep index step) , class classes ] []
Mesajele sunt tipuri obișnuite, dar le-am definit ca tipul care provoacă actualizări, care este convenția în Elm. În Update.elm urmărim arhitectura Elm pentru a gestiona modificările stării modelului. Funcția noastră de actualizare va prelua un Msg
și Model
actual și va returna un nou model și, eventual, o comandă. Comenzile gestionează efectele secundare, pe care le vom analiza în partea a doua. Știm că vom avea mai multe tipuri de Msg
, așa că am configurat un bloc de cazuri de potrivire a modelelor. Acest lucru ne obligă să ne gestionăm toate cazurile, separând, de asemenea, fluxul de stare. Iar compilatorul va fi sigur că nu pierdem niciun caz care ar putea schimba modelul nostru.
Actualizarea unei înregistrări în Elm se face puțin diferit decât actualizarea unui obiect în JavaScript. Nu putem schimba direct un câmp din înregistrare, cum ar fi record.field = *
, deoarece nu putem folosi this
sau self
, dar Elm are ajutoare încorporate. Având în vedere o înregistrare precum brian = { name = "brian" }
, putem actualiza câmpul de nume ca { brian | name = "BRIAN" }
{ brian | name = "BRIAN" }
. Formatul urmează { record | field = newValue }
{ record | field = newValue }
.
Iată cum se actualizează câmpurile de nivel superior, dar câmpurile imbricate sunt mai complicate în Elm. Trebuie să ne definim propriile funcții de ajutor, așa că vom defini patru funcții de ajutor pentru a explora înregistrările imbricate:
- Unul pentru a comuta valoarea pasului
- Unul pentru a returna o nouă secvență, care conține valoarea pasului actualizată
- Altul pentru a alege cărei piese îi aparține secvența
- Și o ultimă funcție pentru a returna o nouă pistă, care conține secvența actualizată care conține valoarea pasului actualizată
Începem cu ToggleStep
pentru a comuta valoarea pasului secvenței de piese între On și Off. Folosim din nou un bloc let...in
pentru a face funcții mai mici în instrucțiunea case. Dacă pasul este deja Dezactivat, îl facem Activat și invers.
toggleStep = if step == Off then On else Off
toggleStep
va fi apelat din newSequence
. Datele sunt imuabile în limbaje funcționale, așa că, în loc să modificăm secvența, creăm de fapt o nouă secvență cu o valoare de pas actualizată pentru a o înlocui pe cea veche.
newSequence = Array.set index toggleStep selectedTrack.sequence
newSequence
folosește Array.set
pentru a găsi indexul pe care vrem să-l comutăm, apoi creează noua secvență. Dacă set nu găsește indexul, returnează aceeași secvență. Se bazează pe selectedTrack.sequence
pentru a ști ce secvență să modifice. selectedTrack
este funcția noastră de ajutor cheie folosită, astfel încât să putem accesa înregistrarea noastră imbricată. În acest moment, este surprinzător de simplu, deoarece modelul nostru are doar o singură pistă.
selectedTrack = model.track
Ultima noastră funcție de ajutor conectează restul. Din nou, deoarece datele sunt imuabile, înlocuim întreaga noastră pistă cu o nouă pistă care conține o nouă secvență.
newTrack = { selectedTrack | sequence = newSequence }
newTrack
este numit în afara blocului let...in
, unde returnăm un nou model, care conține noua pistă, care redă din nou vizualizarea. Nu transmitem efecte secundare, așa că folosim din nou Cmd.none
. Întreaga noastră funcție de update
arată astfel:
update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of ToggleStep index step -> let selectedTrack = model.track newTrack = { selectedTrack | sequence = newSequence } toggleStep = if step == Off then On else Off newSequence = Array.set index toggleStep selectedTrack.sequence in ( { model | track = newTrack } , Cmd.none )
Când rulăm programul nostru, vedem o pistă redată cu o serie de pași. Făcând clic pe oricare dintre butoanele pasului, declanșează ToggleStep
, care accesează funcția noastră de actualizare pentru a înlocui starea modelului.
Pe măsură ce aplicația noastră crește, vom vedea cum modelul repetabil al Arhitecturii Elm face starea de manipulare simplă. Familiaritatea în funcțiile de model, actualizare și vizualizare ne ajută să ne concentrăm asupra domeniului nostru de afaceri și ne face ușor să accesăm aplicația Elm a altcuiva.
A lua o pauza
A scrie într-o limbă nouă necesită timp și practică. Primele proiecte la care am lucrat au fost simple clone TypeForm pe care le-am folosit pentru a învăța sintaxa Elm, arhitectura și paradigmele de programare funcțională. În acest moment, ați învățat deja suficient pentru a face ceva similar. Dacă sunteți nerăbdător, vă recomand să lucrați prin Ghidul oficial de pornire. Evan, creatorul lui Elm, vă prezintă motivațiile pentru Elm, sintaxă, tipuri, arhitectura Elm, scalare și multe altele, folosind exemple practice.
În partea a doua ne vom scufunda în una dintre cele mai bune caracteristici ale lui Elm: folosirea compilatorului pentru a refactoriza secvențatorul nostru de pași. În plus, vom învăța cum să gestionăm evenimentele recurente, folosind comenzi pentru efecte secundare și interacționând cu JavaScript. Rămâneți aproape!