I dagens värld av Javascript Application Framework är designfilosofin den viktigaste differentieringsfaktorn. Om du jämför de populära JS-ramarna, som EmberJS, AngularJS, Backbone, Knockout, etc., är du säker på att hitta skillnader i deras abstraktioner, tänkande modeller och naturligtvis terminologin. Detta är en direkt följd av den underliggande designfilosofin. Men i princip gör de alla en sak, som är att abstrahera DOM på ett sådant sätt att du inte hanterar HTML-element direkt.
Jag tycker personligen att en ram blir intressant när den ger en uppsättning abstraktioner som möjliggör ett annat sätt att tänka. I den här aspekten, reagera, den nya JS-ramen från folk på Facebook, kommer att tvinga dig att ompröva (i viss utsträckning) hur du sönderdelar användargränssnittet och interaktionerna i din ansökan. Efter att ha nått version 0.4.1 (enligt detta skrivande) ger React en överraskande enkel men ändå effektiv modell för att bygga JS-appar som blandar en härlig cocktail av olika slag.
I den här artikeln kommer vi att utforska byggblocken i React och omfamna en stil av tänkande som kan verka kontraintetiv vid första försöket. Men, som React docs säger: "Ge det fem minuter" och då kommer du att se hur denna tillvägagångssätt blir mer naturlig.
Berättelsen om React började inom gränserna för Facebook, där den bryggdes en stund. Efter att ha nått ett stabilt nog tillstånd bestämde sig utvecklarna att öppna källan det några månader tillbaka. Intressant är Instagram-webbplatsen även drivs av React Framework.
React närmar sig DOM-abstraktionsproblemet med en något annorlunda take. För att förstå hur det här är annorlunda kan vi snabbt glansa över de tekniker som antagits av de ramar jag nämnde tidigare.
MVC-modellen (Model-View-Controller) är grundläggande för UI-utveckling, inte bara i webapps, men i front-end-applikationer på vilken plattform som helst. Vid webbapps är DOM den fysiska representationen av en vy. DOM själv genereras från en texthtml-mall som dras från en annan fil, en script-block eller en förkompilerad mallfunktion. De Se
är en enhet som bringar textmallen till liv som ett DOM-fragment. Det skapar också händelsehanterare och tar hand om att manipulera DOM-trädet som ett led i sin livscykel.
För Se
För att vara användbar måste den visa vissa data och möjliggöra användarinteraktion. Uppgifterna är Modell
, som kommer från en viss datakälla (en databas, webbtjänst, lokal lagring etc.). Ramar ger ett sätt att "binda" data till vyn, så att ändringar i data återspeglas automatiskt med ändringar i vyn. Denna automatiska process kallas uppgifter bindande och det finns API / tekniker för att göra detta så smidigt som möjligt.
MVC triaden är klar av Kontrollant
, som engagerar Se
och den Modell
och orkestrerar flödet av data (Modell
) in i Se
och användarhändelser ut från Se
, möjligen leda till förändringar i Modell
.
Ramar som automatiskt hanterar flödet av data fram och tillbaka mellan View och Model behåller en intern händelsesslinga. Denna händelsesslinga behövs för att lyssna på vissa användarhändelser, dataändringshändelser, externa utlösare, etc. och sedan avgöra om det finns någon förändring från den tidigare körningen av slingan. Om det finns ändringar, i båda ändar (Visa eller Modell), säkerställer ramverket att båda tas tillbaka i synkronisering.
Med React tar View-delen av MVC-triaden en framträdande roll och rullas in i en enhet som heter Komponent
. Komponenten upprätthåller en immateriell egenskapspåse som heter rekvisita
, och a stat
som representerar användar-driven tillstånd av användargränssnittet. Visningsgenereringsdelen av Komponent
är ganska intressant och möjligen orsaken till att React sticker ut jämfört med andra ramar. Istället för att bygga en fysisk DOM direkt från en mallfil / skript / funktion, Komponent
genererar en mellanliggande DOM som är en stand-in för den verkliga HTML DOM. Ett ytterligare steg tas då för att översätta denna mellanliggande DOM till den verkliga HTML DOM.
Som en del av den mellanliggande DOM-generationen, Komponent
Fäster även händelsehanterare och binder data som finns i rekvisita
och stat
.
Om tanken på en mellanliggande DOM låter lite alien, var inte för orolig. Du har redan sett denna strategi antagen av språk runtime (aka virtuella maskiner) för tolkade språk. Vår egen JavaScript runtime genererar först en mellanrepresentation innan du spitar ut den ursprungliga koden. Detta gäller även för andra VM-baserade språk som Java, C #, Ruby, Python, etc..
React adoptively adopterar denna strategi för att skapa en mellanliggande DOM innan generering av den slutliga HTML DOM. Den mellanliggande DOM är bara ett JavaScript-objektgraf och görs inte direkt. Det finns ett översättningssteg som skapar den verkliga DOM. Detta är den bakomliggande tekniken som gör React att göra snabba DOM-manipulationer.
För att få en bättre bild av hur React gör det hela jobbet, låt oss dyka lite djupare; börjar med Komponent
. Komponenten är det primära byggstenen i React. Du kan komponera användargränssnittet för din ansökan genom att montera ett träd av komponenter. Varje komponent ger en implementering för göra()
metod, där den skapar mellanliggande DOM. Kallelse React.renderComponent ()
på roten Komponent resulterar i rekursivt att gå ner i komponent-trädet och bygga upp mellanliggande DOM. Den mellanliggande DOM konverteras sedan till den verkliga HTML DOM.
Eftersom den mellanliggande DOM-skapelsen är en integrerad del av komponenten, ger React en bekväm XML-baserad förlängning till JavaScript, kallad JSX, för att bygga komponentträdet som en uppsättning XML-noder. Detta gör det lättare att visualisera och motivera DOM. JSX förenklar också associeringen av händelsehanterare och egenskaper som xml-attribut. Eftersom JSX är ett förlängningsspråk finns det ett verktyg (kommandorad och i webbläsare) för att skapa den slutliga JavaScript. En JSX XML-nod kartläggs direkt till en komponent. Det är värt att påpeka att React fungerar oberoende av JSX och JSX-språket gör det enkelt att skapa mellanliggande DOM.
Kärnreaktionsramen kan laddas ner från deras hemsida. Dessutom, för JSX → JS-omvandlingen kan du antingen använda JSXTransformer i webbläsaren eller använda kommandoradsverktyget, som kallas reaktionsverktyg (installerat via NPM). Du behöver en installation av Node.js för att ladda ner den. Kommandoradsverktyget gör att du kan förkompilera JSX-filerna och undvika översättningen i webbläsaren. Detta rekommenderas definitivt om dina JSX-filer är stora eller många i antal.
Okej, vi har sett mycket teori hittills, och jag är säker på att du kliar för att se någon riktig kod. Låt oss dyka in i vårt första exempel:
/ ** @jsx React.DOM * / var Simple = React.createClass (getInitialState: function () return count: 0;, handtagMouseDown: function () alert ('Jag fick höra:' + this. props.message); this.setState (count: this.state.count + 1);, render: funktion () returnera; ); React.renderComponent (Ge mig meddelandet!Meddelandet förmedlas This.state.count tid (s), document.body);
Även om det är enkelt täcker koden ovan en bra mängd av React-ytan:
React.createClass
och passerar i ett objekt som genomför vissa kärnfunktioner. Den viktigaste är den göra()
, som genererar mellanliggande DOM.
syntax är användbar för att inkorporera JavaScript-uttryck för attribut (onMouseDown = this.handleClick
) och barn-noder (This.state.count
). Eventhanterare associerade med -syntaxen är automatiskt bundna till förekomsten av komponenten. Således detta
Inom händelsehanteringsfunktionen avses komponentinstansen. Kommentaren på första raden / ** @jsx React.DOM * /
är en cue för JSX-transformatorn för att göra översättningen till JS. Utan denna kommentarlinje kommer ingen översättning att äga rum. Vi kan köra kommandoradsverktyget (JSX) i klockläge och automatiskt kompilera ändringar från JSX → JS. Källfilerna finns i / src mappen och utgången genereras i /bygga.
jsx - watch src / build /
Här är den genererade JS-filen:
/ ** @jsx React.DOM * / var Simple = React.createClass (displayName: 'Simple', getInitialState: funktion () return count: 0;, handtagMouseDown: function () alert berättade: '+ this.props.message); this.setState (count: this.state.count + 1);, render: function () return React.DOM.div (null, React.DOM.div (className: "clicker", onMouseDown: this.handleMouseDown, "Ge mig meddelandet!"), React.DOM.div (className: "message", "Meddelande förmedlad", React.DOM.span ( className: "count", this.state.count), "tid (s)"));); React.renderComponent (Simple (message: "Keep it Simple"), document.body);
Lägg märke till hur och
tags map till instanser av
React.DOM.div
och React.DOM.span
.
handleMouseDown
, vi använder oss av this.props
att läsa meddelande egendom som passerade in. Vi satte in meddelande på sista raden i utdraget, i samtalet till React.renderComponent ()
där vi skapar
komponent. Meningen med this.props
är att lagra data som skickades in till komponenten. Det anses vara oföränderligt och endast en högre komponent får göra ändringar och skicka den ner i komponentträdet.handleMouseDown
vi ställer också in vissa användarstatus med this.setState ()
för att spåra hur många gånger meddelandet visades. Du märker att vi använder this.state
i göra()
metod. När som helst du ringer sets ()
, Reakt utlöser också göra()
metod för att hålla DOM synkroniserat. Förutom React.renderComponent ()
, sets ()
är ett annat sätt att tvinga en visuell uppdatering.De händelser som exponeras på mellanliggande DOM, som t.ex. onMouseDown
, fungerar också som ett skikt av indirection innan de sätts på den verkliga DOM. Dessa händelser kallas sålunda som Syntetiska händelser. React adopterar händelsesdelegation, som är en välkänd teknik, och fäster händelser bara på root-levelen av real-DOM. Således finns det bara en sann händelsehanterare på real-DOM. Dessutom ger dessa syntetiska händelser en grad av konsistens genom att gömma webbläsar- och elementskillnader.
Kombinationen av mellanliggande DOM och syntetiska händelser ger dig ett vanligt och konsekvent sätt att definiera användargränssnitt över olika webbläsare och till och med enheter.
Komponenter i React-ramen har en specifik livscykel och utgör en statlig maskin som har tre distinkta tillstånd.
Komponenten kommer till liv efter att ha blivit Monterad. Monteringen resulterar i att gå igenom ett renderingspass som genererar komponent-trädet (mellanliggande DOM). Detta träd konverteras och placeras i en behållare-nod av den verkliga DOM. Detta är ett direkt resultat av samtalet till React.renderComponent ()
.
När den är monterad, stannar komponenten i Uppdatering stat. En komponent blir uppdaterad när du ändrar tillstånd med sets ()
eller ändra rekvisita med setProps ()
. Detta resulterar i sin tur göra()
, vilket gör att DOM synkroniseras med data (rekvisita
+ stat
). Mellan följande uppdateringar beräknar React deltaet mellan föregående komponenttree och det nybildade trädet. Detta är ett mycket optimerat steg (och ett flaggskeppsfunktion) som minimerar manipulationen på den verkliga DOM.
Det slutliga tillståndet är omonterad. Detta händer när du uttryckligen ringer React.unmountAndReleaseReactRootNode ()
eller automatiskt om en komponent var ett barn som inte längre genererades i en göra()
ring upp. Oftast behöver du inte ta itu med detta och bara låta React göra rätt.
Nu skulle det ha varit en stor remiss, om React inte berättade när det rörde sig mellan Monterad-Update-Omonterad stater. Lyckligtvis är det inte så, och det finns krokar som du kan åsidosätta för att bli informerad om livscykeländringar. Namnen talar för sig själva:
getInitialState ()
: Förbered initialtillståndet för komponentencomponentWillMount ()
componentDidMount ()
componentWillReceiveProps ()
shouldComponentUpdate ()
: användbar om du vill kontrollera när en rendering ska hoppas över. componentWillUpdate ()
göra()
componentDidUpdate ()
componentWillUnmount ()
De componentWill *
metoder kallas innan staten förändras och componentDid *
metoderna kallas efter.
Inom ett komponent-träd bör data alltid strömma ner. En föräldrakomponent ska ställa in rekvisita
av en barnkomponent för att överföra data från föräldern till barnet. Detta kallas som Ägare-Owned par. Å andra sidan kommer användarhändelser (mus, tangentbord, berör) alltid att bubbla upp från barnet hela vägen till rotkomponenten, om inte hanteras mellan.
När du skapar mellanliggande DOM i göra()
, du kan också tilldela en ref
egendom till en barnkomponent. Du kan då hänvisa till det från föräldern med hjälp av refs
fast egendom. Detta är avbildat i biten nedan.
render: funktion () // Ange en återgångThis.state.count; handtagMouseDown: funktion () // Använd ref console.log (this.refs.counter.innerHTML); ,
Som en del av komponentmetadata kan du ställa in initialtillståndet (getInitialState ()
), som vi såg tidigare inom livscykelmetoderna. Du kan också ställa in standardvärdena för rekvisita med getDefaultProps ()
och upprätta också några valideringsregler för dessa rekvisita med hjälp av propTypes
. Dokumenten ger en fin översikt över de olika typerna av valideringar (typ kontroller, obligatoriska etc.) som du kan utföra.
React stöder också begreppet a Blanda i att extrahera återanvändbara beteenden som kan injiceras i olika komponenter. Du kan skicka mixins med hjälp av mixins
egenskap hos en komponent.
Nu kan vi bli verkliga och bygga en mer omfattande komponent som använder dessa funktioner.
I det här exemplet kommer vi att bygga en redaktör som accepterar en enkel DSL (Domain Specific Language) för att skapa former. När du skriver in kommer du att se motsvarande utdata på sidan, vilket ger dig live feedback.
DSL låter dig skapa tre typer av former: Ellipse, Rektangel och Text. Varje form anges på en separat linje tillsammans med en massa stylingsegenskaper. Syntaxen är okomplicerad och lånar lite från CSS. För att analysera en rad använder vi en Regex som ser ut som:
var formRegex = / (rect | ellipse | text) (\ s [a-z] +: \ s [a-z0-9] +;) * / i;
Som ett exempel beskriver följande uppsättning linjer två rektanglar och en textetikett ...
// Reagera etiketttextvärde: React; färg: # 00D8FF; typsnittstorlek: 48px; textskugga: 1px 1px 3px # 555; vaddering: 10px; vänster: 100px; topp: 100px; // left logo rekt bakgrund: url (react.png) no-repeat; gränsen: ingen; bredd: 38; höjd: 38; vänster: 60px; topp: 120px; // rätt logo rekt bakgrund: url (react.png) no-repeat; gränsen: ingen; bredd: 38; höjd: 38; vänster: 250px; topp: 120px;
... genererar produktionen som visas nedan:
Okej, låt oss fortsätta och bygga denna redaktör. Vi kommer att börja med HTML-filen (index.html
), där vi lägger upp toppnivån och inkluderar bibliotek och applikationsskript. Jag visar bara relevanta delar här:
I ovanstående kod, den behållare
div håller vår React genererade DOM. Våra applikationsskript ingår från /bygga
katalogen. Vi använder JSX inom våra komponenter och kommandoradenJSX
), lägger de konverterade JS-filerna in i /bygga
. Observera att det här klockarkommandot är en del av reagerar-tools
NPM-modulen.
jsx - watch src / build /
Redigeraren är uppdelad i en uppsättning komponenter, som listas nedan:
mixins
fast egendom.Förhållandet mellan dessa enheter visas i det annoterade komponent-trädet:
Låt oss titta på implementeringen av några av dessa komponenter, från och med ShapeEditor.
/ ** @jsx React.DOM * / var ShapeEditor = React.createClass (componentWillMount: function () this._parser = ny ShapeParser ();, getInitialState: function () return text: ";, render: funktion () var former = this._parser.parse (this.state.text); var tree = (); återvänd träd , handleTextChange: funktion (händelse) this.setState (text: event.target.value));
Som namnet antyder, ger ShapeEditor redigeringsupplevelsen genom att generera och live feedback på
onChange
händelse (händelser i React kallas alltid med camel case) på och vid varje förändring sätter du
text
egenskapen hos komponentens stat
. Som tidigare nämnts, när du ställer in staten med sets ()
, render kallas automatiskt. I det här fallet göra()
av ShapeEditor heter där vi analyserar texten från staten och bygger upp formerna. Observera att vi börjar med ett initialt tillstånd med tom text, som anges i getInitialState ()
krok.
För att analysera texten i en uppsättning former använder vi en instans av ShapeParser
. Jag har släppt ut detaljerna i parsern för att hålla diskussionen inriktad på React. Parser-instansen skapas i componentWillMount ()
krok. Detta kallas precis före komponentfästena och är ett bra ställe att göra några initialiseringar innan den första renderingen händer.
Det rekommenderas vanligen att du tränar all din komplexa bearbetning genom göra()
metod. Händelseshanterare ställer bara in staten medan göra()
är navet för all din kärnlogik.
De ShapeEditor
använder den här idén att göra parsing inuti dess göra()
och vidarebefordrar de detekterade formerna genom att ställa in former
egenskapen hos ShapeCanvas
. Så här rinner data ner i komponentträdet, från ägaren (ShapeEditor
) till den ägda (ShapeCanvas
).
En sista sak att notera här är att vi har den första raden kommentaren för att ange JSX → JS översättning.
Därefter fortsätter vi vidare till ShapeCanvas och komponenten Ellipse, Rektangel och Text.
p> ShapeCanvas
är ganska enkelt med sitt kärnansvar att generera respektive
,
och
komponenter från de överförda formdefinitionerna (this.props.shapes
). För varje form passerar vi i de analyserade egenskaperna med attribututtrycket: egenskaper = shape.properties
.
/ ** @jsx React.DOM * / var ShapeCanvas = React.createClass (getDefaultProps: funktion () return former: [];, render: funktion () var self = this; var shapeTree =this.props.shapes.map (funktion (er) return self._createShape (s);); var noTree =Inga former hittades; returnera this.props.shapes.length> 0? shapeTree: noTree; , _createShape: funktion (form) returnera this._shapeMap [shape.type] (form); , _shapeMap: ellipse: funktion (form) return; , rekt: funktion (form) return ; , text: funktion (form) return ; );
En sak annorlunda här är att vårt komponentträd inte är statiskt, som vi har i ShapeEditor. I stället genereras det dynamiskt genom att slinga över passerat i former. Vi visar också "Inga former hittades"
meddelande om det inte finns något att visa.
Alla former har en liknande struktur och skiljer sig endast från stilen. De använder också av ShapePropertyMixin
att hantera stilgenerationen.
Här är Ellipse:
/ ** @jsx React.DOM * / var Ellipse = React.createClass (mixins: [ShapePropertyMixin], render: funktion () var style = this.extractStyle (true); style ['border-radius'] = ' 50% 50% '; returnera ; );
Genomförandet för extractStyle ()
tillhandahålls av ShapePropertyMixin
.
Rektangelkomponenten följer passar, naturligtvis utan border-radius stil. Textkomponenten har en extra egenskap som heter värde
som sätter den inre texten för .
Här är Text, för att klargöra detta:
/ ** @jsx React.DOM * / var Text = React.createClass (mixins: [ShapePropertyMixin], render: funktion () var style = this.extractStyle (false); returnThis.props.properties.value; );
app.js
är där vi tar allt tillsammans. Här gör vi rotkomponenten, den ShapeEditor
och ger också stöd för att växla mellan några få exempel. När du väljer ett annat prov från nedrullningen lägger vi in någon fördefinierad text i ShapeEditor
och orsaka ShapeCanvas
att uppdatera. Detta händer i readShapes ()
metod.
/ ** @jsx React.DOM * / var shapeEditor =; React.renderComponent (shapeEditor, document.getElementsByClassName ("container") [0]); val (), text = SHAPES [file] || "; $ ('. editor') .val (text); shapeEditor.setState ( text: text); // force a render $ ('.former-plockare'). ändra (readShapes); readShapes (); // ladda tid
Att utöva den kreativa sidan är här en robot som byggdes med hjälp av formredigeraren:
Phew! Detta har varit en ganska lång artikel och efter att ha nått denna punkt borde du ha en känsla av prestation!
Vi har undersökt många koncept här: Komponenternas integrerade roll i ramen, användningen av JSX för att enkelt beskriva ett komponentträd (aka intermediär DOM), olika krokar för att ansluta till komponentens livscykel, användning av stat
och rekvisita
att driva renderingsprocessen, använda Mixins för att regenerera återanvändningsbeteende och slutligen dra allt detta tillsammans med Shape Editor-exemplet.
Jag hoppas att den här artikeln ger dig tillräckligt med stöd för att bygga några React-program för dig själv. För att fortsätta utforskningen är här några praktiska länkar: