React
Tee päivitetty tehtävä TIEA2120-kurssin sivulta.
- Käy ensimmäisenä läpi erinomainen Learning React.js: Getting Started and Concepts -tutoriaali. Tutoriaali on kirjoitettu hieman vanhemmalla Reactin versiolla v0.11 ja joitakin asioita on muuttunut. Esim. renderComponent-metodi on uudemmassa Reactissa ReactDOM.render, JSXTransformeria ei enää ole vaan transformointi tehdään Babelilla Lue myös ajatuksella läpi Thinking in React.
- Asenna firefoxiin React Developer Tools
- Käytä apunasi valmista hello.zip-pakettia josta löytyy valmis
html-tiedosto, johon on linkitetty kaikki tarpeellinen.
- hello.html sisältää valmiin dokumenttipohjan jota voit tarpeen mukaan muutella
- Dokumenttiin on react-kirjastojen lisäksi linkitetty babel.js eli babel-kirjasto muuntaa lennosta kirjoittamasi JSX-koodin tavalliseksi selainyhteensopivaksi javascriptiksi jos skriptin tyyppi on text/babel. Babel osaa muuntaa myös viimeisimmän Ecmascript 2015-standardin mukaisen koodin jos haluat käyttää uusimpia javascript-ominaisuuksia. Tuotantokäytössä babelin tekemä muunnos pitää suorittaa jo www-palvelimessa.
- Dokumenttiin on linkitetty myös jQuery-kirjasto joten voit käyttää kaikkia jQueryn-ominaisuuksia
- Avaa näkyville myös Reactin omat dokumentaatiot ja erityisesti , Tutorial, Top-Level API .
- Ole erityisen tarkkana, että Reactilla kirjoittamasi html on ehjää eli lopetustagit ym. pitää olla paikoillaan. Babelin JSX-tulkki kaatuu heti jos html ei ole rakenteeltaan ehjää. React-komponenttien nimet alkavat isoilla kirjaimilla ja tavallisten html-elementtien nimet pienillä kirjaimilla.
- React/JSX antaa joskus virheilmoitukset aivan eri kohdasta koodia kuin missä varsinainen virhe sijaitsee. Virhe on todennäköisesti käytetyssä komponentissa mutta virheilmoitus viittaa johonkin omituiseen paikkaan komponenttia käyttävässä metodissa. Oikea virhekohta löytyy kun kokeilee mikä komponentti virheen aiheuttaa ja sen jälkeen tutkii tarkemmin tämän komponentin koodia.
- Käy läpi Reactin Tutorial: Intro To React ja yritä toteuttaa tutoriaalissa selitetty ohjelma.
- Toteuta tutoriaalin esimerkkien avulla yksinkertainen autolaskuri
eli painike, joka kasvattaa www-sivulla näkyvää lukua. Toteuta
autolaskuri kokonaan React.js:n avulla
jsx-kielellä.
- Luo ensin oma React-komponentti, joka sisältää yhden tilamuuttujan (laskuri) ja render-metodin, joka tulostaa
laskurimuuttujan arvon. Tila pitää alustaa getInitialState-funktiossa.
var Counter = React.createClass({ getInitialState: function () { return { laskuri: 0 }; }, render: function () { return (<div>{this.state.laskuri}</div>); } });
Oman komponentin lisäksi tarvitset ReactDOM.render-funktiokutsun, joka hoitaa komponenttisi esittämisen sivulla halutun elementin (id="laskuri") sisällä.<div id="laskuri"></div>
Komponentit pitää määritellä ennen ReactDOM.render-kutsua eli ReactDOM.render-kutsun on aina oltava tiedostossa viimeisenä:ReactDOM.render( <Counter />, document.getElementById('laskuri') );
- Lisää komponenttiisi varsinainen laskuri ominaisuus eli painike, joka kasvattaa laskurin arvoa.
Tarvitset komponenttiin metodin, joka kasvattaa laskuria ja render-funktioon sinun pitää lisätä click-käsittelijä, joka sitten kutsuu
metodiasi. Komponentin tilan muuttaminen pitää AINA tehdä this.setstate-metodin avulla:
kasvataLaskuria: function () { this.setState(function(state) { return {laskuri: state.laskuri + 1}; }); }
kasvataLaskuria-metodin kutsuminen onnistuu helposti:render: function () { return (<div> <p>{this.state.laskuri}</p> <button onClick={this.kasvataLaskuria}>Lisää</button> </div>); }
- Luo ensin oma React-komponentti, joka sisältää yhden tilamuuttujan (laskuri) ja render-metodin, joka tulostaa
laskurimuuttujan arvon. Tila pitää alustaa getInitialState-funktiossa.
- Huomioi Reactista seuraavat seikat:
- this.props sisältää komponentille attribuutteina tuodut parametrit. Näiden arvoja ei voi itse ohjelmakoodissa muuttaa.
- this.state sisältää komponentin tilaan liittyvät muuttujat. Niitä voi ja pitää muuttaa mutta muuttamisen saa tehdä vain this.setState-metodilla. Jos muutat komponentin tilaa niin React päivittää muutokset automaattisesti näkyville kutsumalla render-metodia aina kun state muuttuu.
- Lisää autolaskuriin alapuolelle listaus automalleista. Luo oma lista-komponentti jolle tuot parametrina (this.props.items) listassa esitettävät automallit (Polo, Micra, Corsa, Kadett, 1MW Concept, Fiesta, A1, Model S ).
- Luo ensin List-komponentti. this.props.items sisältää taulukon listattavista asioista.
map()-funktiolla on helppo
muodostaa haluttu lista
var List = React.createClass({ render: function(){ return ( <ul> { this.props.items.map(function(item) { return <li key={item}>{item}</li> }) } </ul> ) } });
Valmista List-komponenttia voit nyt käyttää ReactDOM.render-metodissa tai muiden react-komponenttien render-metodeissa seuraavalla tavalla:<List items={["Polo", "Micra", "Corsa", "Kadett", "1MW Concept", "Fiesta", "A1", "Model S"]}/>
Ts. muuta ReactDOM.render-metodi seuraavanlaiseksi:ReactDOM.render( <div> <Counter /> <List items={["Polo", "Micra", "Corsa", "Kadett", "1MW Concept", "Fiesta", "A1", "Model S"]}/> </div>, document.getElementById('laskuri')
- Kokeile toimiiko listaus. Huomaa myös, että Reactin render-funktio voi palauttaa aina vain yhden html-elementin. Jos haluat palauttaa useampia niin ne pitää sijoittaa esim. yhden div-elementin sisään.
- Luo ensin List-komponentti. this.props.items sisältää taulukon listattavista asioista.
map()-funktiolla on helppo
muodostaa haluttu lista
- Luo uusi FilteredList-komponentti, joka käyttää edellä tekemääsi List-komponenttia ja tee autolistauksesta suodattuva.
Katso mallia Getting started-tutoriaalin esimerkistä.
- Luo ensin uusi komponentti FilteredList
- Käytä komponenttisi render-metodissa jo aiemmin luomaasi List-komponenttia. Lisää FilteredList-komponentin render-metodiin input-elementtii jota voi käyttää suodatukseen
- Lisää FilteredList-komponenttiin getInitialState-metodi, joka alustaa listan parametrina annetulla listalla. this.props.itemsin sisältöä
ei voi muuttaa joten tarvitaan komponentille oma tila, joka taasen on muutettavissa.
getInitialState: function () { return { items: this.props.items } }
- Tee suodatusfunktio. Kts.
filter()-metodi.
filterList: function(event){ // kopioidaan täysi lista suodatettavaksi var updatedList = this.props.items; updatedList = updatedList.filter(function(item){ return item.toLowerCase().search( // etsitään input-kenttään syötettyä arvoa kustakin listan elementistä event.target.value.toLowerCase()) !== -1; }); // päivitetään esitettävä lista this.setState({items: updatedList}); }
- Kutsu suodatusfunktiota jos input-elementin sisältöä muutetaan:
<input type="text" placeholder="Suodata" onChange={this.filterList}/> <List items={this.state.items}/>
- Lisää automallilistaukseen myös valmistajat. Tee suodatus kuitenkin vain automallien perusteella vaikka
listauksessa näkyykin sekä malli että valmistaja.
<FilteredList items={[{key:1, malli:"Polo", valmistaja:"Volkswagen"}, {key:2, malli:"Micra",valmistaja:"Nissan"}, {key:3, malli:"Corsa",valmistaja:"Opel"}, {key:4, malli:"Kadett",valmistaja:"Opel"}, {key:5, malli:"1MW Concept",valmistaja:"Toroidion"}, {key:6, malli:"Fiesta",valmistaja:"Ford"}, {key:7, malli:"A1",valmistaja:"Audi"}, {key:8, malli:"Model S", valmistaja:"Tesla"}]}/>
Huomaa, että nyt joudut muuttamaan myös List-komponentin render-metodia. Et voi enää suoraan vain tulostaa itemiä vaan sinun on tulostettava esim. item.malli jne.
this.props.items.map(function(item) { return <li key={item.key}>{item.malli} {item.valmistaja}</li>
Samaan tapaan sinun on muokattava myös suodatusta.
- Lisää automallilistauksen jokaiselle riville kuva, jota klikkaamalla kyseinen rivi poistetaan kokonaan listauksesta.
Kts. Communicate Between Components ja
Expose Component Functions
- Huomaa, että haluat kutsua eri komponentissa olevaa metodia. List-komponentin on osattava kutsua
FilteredList-komponentissa olevaa metodia, joka poistaa (filteroi) pois valitun alkion. Sinun täytyy
viedä käytettävä metodi attribuuttina List-komponentille, joka sitten pystyy käyttämään sitä this.props-taulukon kautta.
<List items={this.state.items} removeHandler={this.removeItem} />
Lisää FilteredList-komponenttiin seuraavanlainen removeItem-metodi:
removeItem: function(clickedItem){ // enää ei voida käyttää pelkästään this.props.items-listaa, koska sitä ei voida muokata // tarvitaan this.state.all joka sisältää kaikki listattavat objektit ja this.state.items sisältää // filtteroidun version. Muuta getInitialState vastaamaan tätä funktiota. var iList = this.state.all; var fList = this.state.items; // poistaminen hoituu näppärästi filterin avulla // https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/filter iList = iList.filter(function(item){ return !(item.key == clickedItem) }); fList = fList.filter(function(item){ return !(item.key == clickedItem) }); this.setState({ all: iList, items: fList}); }
- Joudut viemään List-komponentin render-funktiossa img-elementin onClick-käsittelyssä parametrina tiedon sen alkion
avaimesta, joka halutaan poistaa. Tätä
ei voi suoraan laittaa generoitavaan html-koodiin, koska silloin
javascript-tulkki kutsuisi suoraan tätä funktiota
eikä vasta riviä klikattaessa. Tämä voidaan kiertää luomalla erikseen
anonyymifunktio, joka kutsuu halutulla parametrilla poistometodia.
Asetetaan tämä funktio List-komponentissa onClick-käsittelijäksi.
render: function(){ // this täytyy ottaa talteen, koska se muuttuu suorituskontekstin mukaan var self = this; return ( <ul> { this.props.items.map(function(item) { var key = item.key; // tarvitaan avain jolla tunnistetaan rivi // luodaan anonyymifunktio jotta voidaan viedä parametrina valitun objektin key var removeHandler = (function(event){ // kutsutaan List-komponentille parametrina tuotua removeHandleria ja viedään parametrina poistettavan objektin key self.props.removeHandler(key); }); ...
Käytetään seuraavasti:
<img onClick={removeHandler} />
Vinkki: Pass props to parent component in React.js
- Huomaa, että haluat kutsua eri komponentissa olevaa metodia. List-komponentin on osattava kutsua
FilteredList-komponentissa olevaa metodia, joka poistaa (filteroi) pois valitun alkion. Sinun täytyy
viedä käytettävä metodi attribuuttina List-komponentille, joka sitten pystyy käyttämään sitä this.props-taulukon kautta.
- Lisää jokaiselle autolistausriville
onClick-käsittely,joka
valitsee klikatun rivin ja korostaa sen. Rivin uudelleen klikkaaminen poistaa valinnan ja korostuksen.
- Lisää FilteredList-luokkaan select-metodi, joka hoitaa rivin valinnan ja valinnan poistamisen. Voit keksiä riville (item, clickedItem) uuden attribuutin selected. Esim. item.selected = True; tarkoittaisi valittua riviä ja item.selected = False; valitsematonta riviä.
- Huomioi item.selected myös List-komponentissa eli palauta erilainen rivi sen mukaan onko rivi valittu vai ei
- Vie FilteredList-luokan select-metodi samaan tapaan parametrina List-komponentille kuin teit poistamisen yhteydessä
- On houkutteleva ajatus lisätä FilteredList-luokkaan uusi tilamuuttuja, joka sisältäisi valittujen rivien lukumäärän. Näin ei kuitenkaan kannata tehdä vaan parempi ratkaisu on laskea valittujen rivien lukumäärä render-metodissa.
- Aseta laskuri lisäämään aina yhtä monta autoa laskuriin kuin on valittuja (korostettuja) rivejä. Jos
yhtään riviä ei ole valittuna niin laskuriakaan ei kasvateta.
Sinun on sijoitettava laskurikomponentti nyt FilteredList-luokan sisään jotta pystyt viemään laskurille parametrina (attribuuttina) tiedon siitä montako autoa kulloinkin on valittuna.
- Lisää sivulle lomake jolla voi lisätä uusia automerkkejä sivulla
näkyvään listaan. Kts.Malli. Valitse merkin valmistaja
valmiista listasta. Tee oma komponentti valintalistasta (select) ja koko
lomakkeesta. Lomakekomponentti käyttää
valintalistakomponenttia ja luo muut lomakkeen tarvitsemat asiat. Lomake
ottaa vastaan tiedon valintalistan valinnasta (onChange) ym. ja
ilmoittaa Lisää-painikkeen painalluksen jälkeen syötetyt arvot
eteenpäin. Lomakekomponentilla on state eli se pitää tallessa lomakkeen
eri osien valinnat. Mitä korkeammalla tasolla saat
saat pidettyä sovelluksen tilan sen parempi. Kaikki komponentit eivät
tarvitse omaa tilaa (state).
Kts. React Forms. Huomaa erityisesti Reactin erikoisuudet Select-elementin kanssa ja defaultValue-attribuutin käyttäminen.
Komponenttisi voisi toimia esim. näin:
<Select name="paikka" items={["Auditorio 1", "Auditorio 2", "Auditorio 3", "Auditorio 4"]} label="Paikka" />
Lue myös React ja silmukat.
- Varmista, että lomake nollaantuu lisäämisen jälkeen alkutilaansa. Tämä hoituu helpoiten muuttamalla lomake-elementille annetun keyn arvoa jolloin react hoksaa, että ahaa nyt on uusi lomake ja päivittää kaiken uudelleen.
- Lisää myös järkevät virheenkäsittelyt. Tarkista lomakekomponentissa syötteiden oikeellisuus ja jos automerkkikenttä on tyhjä niin älä tee lisäystä vaan aseta lomakkeelle virheilmoitus näkyviin.
- Lataa ajaxin avulla automerkkilista www-palvelimelta ja alusta sillä autolaskurissa näkyvä lista. Kirjoita
lista json-muotoiseksi tiedostoksi. Voit käyttää jQuerya tai Reactin omaa rajapintaa:
Load Initial Data via Ajax.
Lisää komponenttiisi componentDidMount-metodi, jota react kutsuu automaattisesti sen jälkeen kun komponentti on renderoitu ensimmäisen kerran. Varmista siis, että komponentissa on myös hyvä oletussisältö vielä ilman json-dataa. Voit ladata json-tiedot aivan samaan tapaan kuin aiemminkin ajax-tehtävissä. Success- ja error-funktiot on ympätty suoraan kutsuun mukaan anonyymifunktioina:
componentDidMount: function() { $.ajax({ url:http://foobar.example/file.json , dataType: 'json', cache: false, success: function(data) { // data sisältää json-datan this.setState({data: data}); }.bind(this), error: function(xhr, status, err) { console.log(status, err.toString()); }.bind(this) }); },
Toinen versio:
componentDidMount: function() { this.serverRequest = $.get("http://foobar.example/file.json", function (result) { // result[0] sisältää palvelimelta saadun json-datan // käsitellään halutulla tavalla var tulos = result[0]; this.setState({ foo: tulos.foo, bar: tulos.bar }); }.bind(this)); // bind(this) tarvitaan jotta kontekstina pysyy edelleen tämä komponentti. Voisi kiertää myös self = this; tavalla },
Palvelimelta tulevan JSON-tiedoston on oltava ehdottomasti syntaktisesti oikein. Tavalliseen javascript-ohjelmakoodiin voi usein kirjoittaa huolettomammin mutta JSON-tiedostossa sama ei käy. Jos ei määritä ajax-kutsuun erikseen virheenkäsittelyä niin React ei anna mitään selkeää virheilmoitusta jos palvelimelta ladatussa tiedostossa on vikaa.
Jos JSON-syntaksin kanssa on ongelmia niin käytä apuna JSONLint-palvelua.
Varmista, että json-tiedostosi mimetyyppi on varmasti application/json, koska muuten ajax-kutsu ei toimi. Tämän voit varmistaa lisäämällä .htaccess-tiedostoon rivin:
Addtype application/json .json
Voit halutessasi kokeilla myös app enginella. web2pyn avulla saa helposti tuotettua JSON-dataa, kun vain muuttaa pyytämänsä sivun osoitteen päätteeksi .json. Tämän jälkeen web2py muuntaa automaattisesti näkymälle palautettavat python-tietorakenteet JSON-muotoon. Tämä edellyttää myös seuraavan rivin lisäämistä ohjelmakoodiin:
#Enable specific generics: response.generic_patterns=['.json']
Esimerkit
Malliesimerkki ja Toinen malli
Esimerkki reactin date-picker-komponentin käytöstä. Huom. pitää erikseen hakea ja ottaa sivulla käyttöön datepicker- ja moment-javascript-kirjastot.
Käyttäjien kommentit