De beste verklaring voor JavaScript-reactiviteit

Veel front-end JavaScript-frameworks (bijv. Angular, React en Vue) hebben hun eigen Reactivity-engines. Door te begrijpen wat reactiviteit is en hoe het werkt, kunt u uw ontwikkelingsvaardigheden verbeteren en JavaScript-kaders effectiever gebruiken. In de video en het onderstaande artikel bouwen we dezelfde soort reactiviteit die u in de Vue-broncode ziet.

Als je deze video bekijkt in plaats van het artikel te lezen, bekijk dan de volgende video in de serie over reactiviteit en proxy's met Evan You, de maker van Vue.

Het reactiviteitssysteem

Het reactiviteitssysteem van Vue kan op magie lijken wanneer je het voor het eerst ziet werken. Neem deze eenvoudige Vue-app:

Op de een of andere manier weet Vue gewoon dat als de prijs verandert, het drie dingen moet doen:

  • Update de prijswaarde op onze webpagina.
  • Bereken de uitdrukking die prijs * hoeveelheid vermenigvuldigt en werk de pagina bij.
  • Roep de functie totalPriceWithTax opnieuw op en werk de pagina bij.

Maar wacht, ik hoor je je afvragen, hoe weet Vue wat te updaten wanneer de prijs verandert, en hoe houdt het alles bij?

Dit is niet hoe JavaScript-programmering meestal werkt

Als het u niet duidelijk is, is het grote probleem dat we moeten aanpakken, dat programmeren meestal niet op deze manier werkt. Als ik bijvoorbeeld deze code voer:

Wat denk je dat het gaat afdrukken? Omdat we Vue niet gebruiken, wordt 10 afgedrukt.

In Vue willen we dat het totaal wordt bijgewerkt wanneer de prijs of hoeveelheid wordt bijgewerkt. Wij willen:

Helaas is JavaScript procedureel, niet reactief, dus dit werkt niet in het echte leven. Om totaal reactief te maken, moeten we JavaScript gebruiken om dingen anders te laten werken.

Probleem

We moeten opslaan hoe we het totaal berekenen, zodat we het opnieuw kunnen uitvoeren wanneer de prijs of hoeveelheid verandert.

Oplossing

Ten eerste hebben we een manier nodig om onze applicatie te vertellen: "De code die ik ga uitvoeren, bewaar deze, misschien moet u hem op een ander tijdstip uitvoeren." Dan willen we de code uitvoeren, en als de prijs of hoeveelheidsvariabelen worden bijgewerkt, voer de opgeslagen code opnieuw uit.

We kunnen dit doen door de functie op te nemen, zodat we deze opnieuw kunnen uitvoeren.

Merk op dat we een anonieme functie opslaan in de doelvariabele en vervolgens een recordfunctie aanroepen. Met de ES6-pijlsyntaxis zou ik dit ook kunnen schrijven als:

De definitie van het record is eenvoudig:

We slaan het doel op (in ons geval de {total = prijs * hoeveelheid}) zodat we het later kunnen uitvoeren, misschien met een herhalingsfunctie die alle dingen uitvoert die we hebben vastgelegd.

Dit doorloopt alle anonieme functies die we in de opslagarray hebben opgeslagen en voert ze allemaal uit.

Dan kunnen we in onze code gewoon:

Eenvoudig genoeg, toch? Hier is de code in zijn geheel als u deze moet doorlezen en nog een keer wilt proberen te begrijpen. Ter informatie, ik codeer dit op een bepaalde manier, voor het geval je je afvraagt ​​waarom.

Probleem

We kunnen zo nodig doelen blijven opnemen, maar het zou leuk zijn om een ​​robuustere oplossing te hebben die kan worden geschaald met onze app. Misschien een klasse die zorgt voor het bijhouden van een lijst met doelen die een melding krijgen wanneer we ze opnieuw moeten uitvoeren.

Oplossing: een afhankelijkheidsklasse

Een manier waarop we dit probleem kunnen beginnen op te lossen, is door dit gedrag in zijn eigen klasse in te kapselen, een afhankelijkheidsklasse die het standaard waarnemingspatroon voor programmering implementeert.

Dus als we een JavaScript-klasse maken om onze afhankelijkheden te beheren (wat meer in de buurt komt van hoe Vue dingen afhandelt), kan het er zo uitzien:

Let op: in plaats van opslag, slaan we nu onze anonieme functies op in abonnees. In plaats van onze opnamefunctie noemen we nu afhankelijk en gebruiken we nu kennisgeving in plaats van opnieuw afspelen. Om dit te laten werken:

Het werkt nog steeds, en nu voelt onze code meer herbruikbaar. Het enige dat nog steeds een beetje raar aanvoelt, is het instellen en uitvoeren van het doelwit.

Probleem

In de toekomst hebben we een Dep-klasse voor elke variabele en het is leuk om het gedrag van het creëren van anonieme functies die moeten worden bekeken voor updates, samen te vatten. Misschien is een watcher-functie mogelijk om voor dit gedrag te zorgen.

Dus in plaats van te bellen:

(dit is slechts de bovenstaande code)

We kunnen in plaats daarvan gewoon bellen:

Oplossing: een Watcher-functie

Binnen onze Watcher-functie kunnen we een paar eenvoudige dingen doen:

Zoals u kunt zien, neemt de watcher-functie een myFunc-argument, stelt dat als onze globale doeleigenschap, dep.depend () aan om ons doel toe te voegen als een abonnee, roept de doelfunctie aan en stelt het doel opnieuw in.

Wanneer we nu het volgende uitvoeren:

Je vraagt ​​je misschien af ​​waarom we target hebben geïmplementeerd als een globale variabele, in plaats van het waar nodig door te geven aan onze functies. Daar is een goede reden voor, die aan het einde van ons artikel duidelijk zal worden.

Probleem

We hebben één Dep-klasse, maar we willen echt dat elk van onze variabelen zijn eigen Dep heeft. Laat me dingen naar eigenschappen verplaatsen voordat we verder gaan.

Laten we er even van uitgaan dat al onze eigenschappen (prijs en hoeveelheid) hun eigen interne Dep-klasse hebben.

Nu wanneer we rennen:

Omdat de data.price-waarde wordt benaderd (wat het is), wil ik dat de Dep-klasse van de prijseigenschap onze anonieme functie (opgeslagen in target) naar zijn subscriber array pusht (door dep.depend () aan te roepen). Omdat data.quantity wordt gebruikt, wil ik ook dat de kwantiteitseigenschap Dep class deze anonieme functie (opgeslagen in het doel) naar de subscriber array duwt.

Als ik nog een anonieme functie heb waarbij alleen data.price wordt gebruikt, wil ik dat alleen naar de prijseigenschap Dep class worden gepusht.

Wanneer wil ik dat dep.notify () op de abonnees van de prijs wordt gebeld? Ik wil dat ze worden gebeld wanneer de prijs is ingesteld. Tegen het einde van het artikel wil ik in de console kunnen gaan en het volgende doen:

We hebben een manier nodig om aan te sluiten op een gegevenseigenschap (zoals prijs of hoeveelheid), zodat we het doel bij toegang kunnen opslaan in onze abonneeserie en wanneer het wordt gewijzigd, voeren de functies onze abonneeserie uit.

Oplossing: Object.defineProperty ()

We moeten meer leren over de functie Object.defineProperty (), gewoon ES5 JavaScript. Hiermee kunnen we getter- en setter-functies voor een eigenschap definiëren. Lemme laat je het basisgebruik zien, voordat ik je laat zien hoe we het gaan gebruiken met onze Dep-klasse.

Zoals u ziet, worden er slechts twee regels vastgelegd. Het krijgt of stelt echter geen waarden in, omdat we de functionaliteit hebben overschreden. Laten we het nu weer toevoegen. get () verwacht een waarde te retourneren, en set () moet nog een waarde bijwerken, dus laten we een variabele internalValue toevoegen om onze huidige prijswaarde op te slaan.

Nu dat onze get en set correct werken, wat denk je dat naar de console wordt afgedrukt?

We hebben dus een manier om meldingen te ontvangen wanneer we waarden krijgen en instellen. En met enige recursie kunnen we dit voor alle items in onze gegevensreeks uitvoeren, toch?

Ter info, Object.keys (data) retourneert een reeks sleutels van het object.

Nu heeft alles getters en setters, en we zien dit op de console.

🛠 Beide ideeën samenbrengen

Wanneer een stukje code zoals dit wordt uitgevoerd en de waarde van prijs krijgt, willen we dat prijs deze anonieme functie (doel) onthoudt. Op die manier zal, als de prijs wordt gewijzigd, of op een nieuwe waarde wordt ingesteld, deze functie worden geactiveerd om opnieuw te worden uitgevoerd, omdat hij weet dat deze lijn ervan afhankelijk is. Dus je kunt er zo over denken.

Get => Onthoud deze anonieme functie, we zullen deze opnieuw uitvoeren wanneer onze waarde verandert.

Set => Voer de opgeslagen anonieme functie uit, onze waarde is zojuist gewijzigd.

Of in het geval van onze Dep Class

Toegang tot prijs (get) => bel dep.depend () om het huidige doel op te slaan

Price set => call dep.notify () op prijs, waarbij alle doelen opnieuw worden uitgevoerd

Laten we deze twee ideeën combineren en onze laatste code doornemen.

En kijk nu wat er in onze console gebeurt als we spelen.

Precies waar we op hoopten! Zowel prijs als hoeveelheid zijn inderdaad reactief! Onze totale code wordt opnieuw uitgevoerd wanneer de waarde van prijs of hoeveelheid wordt bijgewerkt.

Deze illustratie uit de Vue-documenten zou nu zin ​​moeten krijgen.

Zie je die prachtige paarse datacirkel met de getters en setters? Het moet er bekend uitzien! Elke componentinstantie heeft een watcherinstantie (in blauw) die afhankelijkheden van de getters verzamelt (rode lijn). Wanneer een setter later wordt aangeroepen, wordt de watcher hiervan op de hoogte gesteld, waardoor de component opnieuw wordt gerenderd. Hier is de afbeelding opnieuw met enkele van mijn eigen annotaties.

Ja, is dit nu niet veel logischer?

Het is duidelijk hoe Vue dit onder de covers doet, is ingewikkelder, maar je weet nu de basis.

Dus wat hebben we geleerd?

  • Hoe een Dep-klasse te maken die afhankelijkheden verzamelt (afhankelijk) en alle afhankelijkheden opnieuw uitvoert (melden).
  • Hoe een watcher te maken om de code die we gebruiken te beheren, die mogelijk moet worden toegevoegd (doel) als een afhankelijkheid.
  • Hoe Object.defineProperty () te gebruiken om getters en setters te maken.

Wat nu?

Als je het leuk vond om samen met mij te leren over dit artikel, is de volgende stap in je leerpad om te leren over Reactiviteit met proxy's. Bekijk zeker mijn mijn gratis video over dit onderwerp op VueMastery.com waar ik ook spreek met Evan You, de maker van Vue.js.

Oorspronkelijk gepubliceerd op www.vuemastery.com.