Den beste forklaringen på JavaScript-reaktivitet

Mange front-end JavaScript-rammer (Ex. Angular, React og Vue) har sine egne reaktivitetsmotorer. Ved å forstå hva reaktivitet er og hvordan det fungerer, kan du forbedre utviklingsferdighetene dine og mer effektivt bruke JavaScript-rammer. I videoen og artikkelen nedenfor bygger vi den samme typen reaktivitet som du ser i Vue-kildekoden.

Hvis du ser denne videoen i stedet for å lese artikkelen, kan du se den neste videoen i serien som diskuterer reaktivitet og fullmakter med Evan You, skaperen av Vue.

Reaktivitetssystemet

Vues reaktivitetssystem kan se ut som magi når du ser det fungerer for første gang. Ta denne enkle Vue-appen:

På en eller annen måte vet Vue bare at hvis prisen endres, bør den gjøre tre ting:

  • Oppdater prisverdien på hjemmesiden vår.
  • Beregn uttrykket som multipliserer pris * mengde på nytt, og oppdater siden.
  • Ring totalPriceWithTax-funksjonen igjen og oppdater siden.

Men vent, jeg hører du lurer på, hvordan vet Vue hva jeg skal oppdatere når prisen endres, og hvordan holder den oversikt over alt?

Slik fungerer ikke JavaScript-programmering

Hvis det ikke er opplagt for deg, er det store problemet vi må ta opp at programmering vanligvis ikke fungerer på denne måten. Hvis jeg for eksempel kjører denne koden:

Hva tror du det kommer til å trykke? Siden vi ikke bruker Vue, kommer den til å skrive ut 10.

I Vue ønsker vi at total skal oppdateres når pris eller antall oppdateres. Vi vil:

Dessverre er JavaScript prosessuelt, ikke reaktivt, så dette fungerer ikke i det virkelige liv. For å gjøre total reaktiv, må vi bruke JavaScript for å få ting til å oppføre seg annerledes.

Problem

Vi må lagre hvordan vi beregner totalen, slik at vi kan kjøre den på nytt når pris eller mengde endres.

Løsning

Til å begynne med trenger vi en måte å fortelle applikasjonen vår, "Koden jeg skal kjøre, lagre denne, det kan hende jeg trenger at du kjører den på et annet tidspunkt." Så vil vi kjøre koden, og hvis pris eller kvantitetsvariabler blir oppdatert, kjør den lagrede koden igjen.

Vi kan gjøre dette ved å spille inn funksjonen slik at vi kan kjøre den igjen.

Legg merke til at vi lagrer en anonym funksjon inne i målvariabelen, og deretter kaller en postfunksjon. Ved hjelp av ES6 pilsyntaks kunne jeg også skrive dette som:

Definisjonen av posten er ganske enkelt:

Vi lagrer målet (i vårt tilfelle {total = pris * mengde}) slik at vi kan kjøre det senere, kanskje med en replayfunksjon som kjører alle tingene vi har registrert.

Dette går gjennom alle de anonyme funksjonene vi har lagret i lagringsenheten og utfører hver av dem.

I koden vår kan vi bare:

Enkelt nok, ikke sant? Her er koden i sin helhet hvis du trenger å lese gjennom og prøve å forstå den en gang til. FYI, jeg koder dette på en bestemt måte, i tilfelle du lurer på hvorfor.

Problem

Vi kan fortsette å registrere mål etter behov, men det ville være fint å ha en mer robust løsning som skaleres med appen vår. Kanskje en klasse som tar seg av å opprettholde en liste over mål som blir varslet når vi trenger dem for å bli kjørt på nytt.

Løsning: En avhengighetsklasse

En måte vi kan begynne å løse dette problemet på, er ved å innkapsle denne oppførselen til sin egen klasse, en Dependency Class som implementerer standard observasjonsmønster for programmering.

Så hvis vi oppretter en JavaScript-klasse for å administrere avhengighetene våre (som er nærmere hvordan Vue håndterer ting), kan det se slik ut:

Legg merke til i stedet for lagring, lagrer vi nå våre anonyme funksjoner i abonnenter. I stedet for vår opptaksfunksjon kaller vi nå avhengig, og vi bruker nå varsling i stedet for å spille på nytt. For å få dette til å gå:

Det fungerer fortsatt, og nå føles koden vår mer gjenbrukbar. Det eneste som fremdeles føles litt rart, er innstillingen og driften av målet.

Problem

I fremtiden kommer vi til å ha en Dep-klasse for hver variabel, og det vil være fint å innkapsle oppførselen til å lage anonyme funksjoner som må følges for oppdateringer. Kanskje kan en vaktfunksjon være for å ta vare på denne oppførselen.

Så i stedet for å ringe:

(dette er bare koden ovenfra)

Vi kan i stedet bare ringe:

Løsning: En Watcher-funksjon

Inne i Watcher-funksjonen kan vi gjøre noen få enkle ting:

Som du kan se, tar observasjonsfunksjonen et myFunc-argument, setter det som en global målegenskap å kalle dep.depend () for å legge til vårt mål som abonnent, kalle målfunksjonen og tilbakestille målet.

Nå når vi kjører følgende:

Du lurer kanskje på hvorfor vi implementerte mål som en global variabel, i stedet for å overføre det til funksjonene våre der det trengs. Det er en god grunn til dette, som vil bli tydelig ved slutten av artikkelen vår.

Problem

Vi har en enkelt Dep-klasse, men det vi virkelig ønsker er at hver av variablene våre skal ha sin egen Dep. La meg flytte ting til eiendommer før vi går lenger.

La oss anta et øyeblikk at hver av våre eiendommer (pris og mengde) har sin egen interne Dep-klasse.

Nå når vi løper:

Siden data.price-verdien nås (som den er), vil jeg at prisegenskapens Dep-klasse skal skyve vår anonyme funksjon (lagret i mål) på abonnentgruppen (ved å ringe dep.depend ()). Siden data.quantity er tilgjengelig, vil jeg også at mengdeegenskapen Dep-klasse skal skyve denne anonyme funksjonen (lagret i mål) inn i abonnentgruppen.

Hvis jeg har en annen anonym funksjon der bare data.price er tilgjengelig, vil jeg ha det bare til prisegenskapen Dep-klassen.

Når ønsker jeg at dep.notify () skal ringes på prisens abonnenter? Jeg vil at de skal ringes når prisen er satt. Mot slutten av artikkelen vil jeg kunne gå inn på konsollen og gjøre:

Vi trenger en måte å koble til en dataegenskap (som pris eller mengde), så når det er tilgjengelig kan vi lagre målet i abonnentgruppen vår, og når det endres, kjører funksjonene som er lagret abonnentarrayet vårt.

Løsning: Object.defineProperty ()

Vi må lære om funksjonen Object.defineProperty () som er vanlig ES5 JavaScript. Det lar oss definere getter og setter funksjoner for en eiendom. Lemme viser deg den helt grunnleggende bruken, før jeg viser deg hvordan vi skal bruke den med Dep-klassen vår.

Som du ser, logger det bare to linjer. Imidlertid får ikke eller innstiller noen verdier faktisk, siden vi overstyrte funksjonaliteten. La oss legge det tilbake nå. get () forventer å returnere en verdi, og angi () trenger fortsatt å oppdatere en verdi, så la oss legge til en internVariabel for å lagre vår nåværende prisverdi.

Nå som vårt get og sett fungerer som det skal, hva tror du vil skrive ut til konsollen?

Så vi har en måte å bli varslet når vi får og setter verdier. Og med litt rekursjon kan vi kjøre dette for alle elementene i datasettet vårt, ikke sant?

FYI, Object.keys (data) returnerer en rekke tastene til objektet.

Nå har alt getters og setters, og vi ser dette på konsollen.

Å sette begge ideene sammen

Når et stykke kode som dette kjøres og får verdien av pris, ønsker vi at pris skal huske denne anonyme funksjonen (målet). På den måten hvis pris blir endret, eller er satt til en ny verdi, vil den utløse denne funksjonen for å bli kjørt på nytt, siden den vet at denne linjen er avhengig av den. Så du kan tenke på det slik.

Få => Husk denne anonyme funksjonen, vi kjører den igjen når verdien endres.

Sett => Kjør den lagrede anonyme funksjonen, verdien vår ble akkurat endret.

Eller for Dep Class

Pris tilgjengelig (få) => ring dep.depend () for å lagre det nåværende målet

Prissett => samtale dep.notify () på pris, kjører alle målene på nytt

La oss kombinere disse to ideene og gå gjennom den endelige koden.

Og se nå på hva som skjer i konsollen vår når vi leker.

Akkurat det vi håpet på! Både pris og mengde er virkelig reaktive! Den totale koden vår kjøres på nytt når verdien på pris eller antall blir oppdatert.

Denne illustrasjonen fra Vue-dokumentene burde begynne å være fornuftige nå.

Ser du den vakre lilla Datasirkelen med getters og setters? Det skal se kjent ut! Hver komponentforekomst har en forekomster av blikk (i blått) som samler avhengigheter fra getters (rød linje). Når det kalles en setter senere, varsler den vakteren som får komponenten til å gjengis på nytt. Her er bildet igjen med noen av mine egne merknader.

Ja, er det ikke noe mer fornuftig nå?

Åpenbart hvordan Vue gjør dette under dekslene er mer komplisert, men du vet nå det grunnleggende.

Så hva har vi lært?

  • Slik lager du en Dep-klasse som samler avhengigheter (avhenger) og kjører alle avhengigheter på nytt (varsles).
  • Hvordan lage en vaktmester for å administrere koden vi kjører, som kanskje må legges til (mål) som en avhengighet.
  • Hvordan bruke Object.defineProperty () til å lage bokstaver og settere.

Hva nå?

Hvis du likte å lære med meg på denne artikkelen, er neste trinn i læringsveien å lære om reaktivitet med proxy. Sjekk absolutt min gratis video om dette emnet på VueMastery.com, der jeg også snakker med Evan You, skaperen av Vue.js.

Opprinnelig publisert på www.vuemaster.com.