React
React on Metan (Facebook) kehittämä javascript-kirjasto käyttöliittymien tekemiseen.
Tämä sivu käyttää funktiokomponentteja ja koukkuja. Jos haluat perusteellisemman esityksen koukuista, katso React ja koukut.
- Esimerkki funktiokomponenteilla (malli.js)
- Laskuriesimerkki (laskuri.js)
- Kontrolloitu lomake (controlled.js)
- Kontrolloimaton lomake (uncontrolled.js)
- Validointiesimerkki (validation.js)
- Refs ja DOM-elementit (refs.js)
Kannattaa katsoa myös React Functional Components, Props, and JSX.
- React päivittää käyttöliittymän automaattisesti kun data muuttuu
- React osaa päivittää käyttöliittymästä vain ne osat, jotka liittyvät muuttuneeseen dataan
- Reactilla toteutettu käyttöliittymä muodostuu koostetuista komponenteista. React ei käytä sabluunoita vaan javascriptia ja JSX:ää
- React vastaa MVC-arkkitehtuurissa näkymää (V)
- Thinking in React
JSX
- JSX on hieman XML-kieleltä näyttävää javascriptia.
- JSX:n avulla voi kirjoittaa XML-tyylistä koodia, jossa on sekaisin
html-elementtejä ja React-komponentteja. Html-elementit kirjoitetaan pienillä
kirjaimilla, React-komponenttien nimet aloitetaan isolla kirjaimella.
<h1>Otsikko</h1> <OmaKomponentti /> <OmaLista alkiot={[1, 2, 3]} /> - JSX muunnetaan tavalliseksi javascript-koodiksi. Muunnoksen voi tehdä selaimessa lennosta, mutta tuotantokäytössä koodi käännetään etukäteen.
- Attribuutissa oleva javascript-lauseke kirjoitetaan aaltosulkeiden sisään. Lainausmerkkejä ei silloin käytetä.
- React toimii paikoin eri tavalla kuin tavallinen DOM-rajapinta. Lue Common components.
Hello world
Yksinkertaisimmassa React-ohjelmassa liitetään html-dokumenttiin react-kirjasto, Babel JSX-muunnosta varten, ja kirjoitetaan script-elementin sisään oma komponentti ja sen renderöinti. Huomaa, että script-elementin type-attribuutille on annettava arvo: Babel muuntaa vain skriptit, joiden tyyppi on text/babel tai text/jsx. Tavallinen javascript-skripti jätetään rauhaan.
Kts. Quick Start ja Tutorial.
<!DOCTYPE html>
<html lang="fi">
<head>
<meta charset="utf-8" />
<title>Hello World</title>
<!-- Babel muuntaa JSX:n lennossa. React itse tuodaan skriptissä. -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="main"></div>
<script type="text/babel" data-type="module">
// React 19 ladataan ESM-muodossa. React-oletustuonti tarvitaan, koska Babel
// kääntää JSX:n React.createElement-kutsuiksi.
import React from "https://esm.sh/react@19";
import { createRoot } from "https://esm.sh/react-dom@19/client";
// Yksinkertainen komponentti, joka palauttaa tekstikappaleen.
// Komponentille annetut parametrit löytyvät props-parametrista.
const HelloWorld = function (props) {
return (
<p>
Hello {props.name}!
</p>
);
};
// Näytetään main-tunnisteella varustetun lohkon sisällä edellä luotu komponentti.
// Esitettävä nimi annetaan attribuuttina.
const juuri = createRoot(document.getElementById("main"));
juuri.render(<HelloWorld name="Tommi" />);
</script>
</body>
</html>
Kaksi yksityiskohtaa kannattaa huomata. data-type="module" kertoo Babelille, että skriptissä käytetään import-lauseita. Ja React ladataan esm.sh-palvelusta ESM-muodossa, koska React 19:stä lähtien ei enää julkaista script-elementillä ladattavia UMD-paketteja. Vanhoissa esimerkeissä näkee vielä muodon
<script crossorigin="anonymous" src="https://unpkg.com/react@18/umd/react.development.js"></script>
joka toimii vain React 18:aan asti.
Funktiokomponentit
const Komponentti = function (props) {
return (<p>Hello World!</p>);
};
- Komponentti voi koostua muista komponenteista ja html-elementeistä.
- Komponentille annetut parametrit eli elementin attribuutit löytyvät
props-parametrista. Niiden arvoja ei saa itse muuttaa.
const Komponentti = function (props) { return (<p>Hello {props.nimi}</p>); }; - Komponentin tilaan voi tallentaa mitä tahansa arvoja.
useState-kutsu palauttaa kaksi asiaa: tilamuuttujan ja funktion,
jolla sitä muutetaan. Koukut tuodaan nimettyinä:
import { useState } from "react".const Laskuri = function (props) { const [laskuri, setLaskuri] = useState(0); return (<p>{laskuri}</p>); }; - Jos suinkin mahdollista, älä käytä komponentissasi tilaa. Yritä sijoittaa sovelluksen tilaan liittyvät muuttujat komponenttihierarkiassa mahdollisimman korkealle ja vain yhteen paikkaan. Kts. Sharing state between components.
- Passing props to a component
- Tapahtumankäsittely on yksinkertaista:
const Laskuri = function (props) { const [laskuri, setLaskuri] = useState(0); const kasvata = function () { setLaskuri(laskuri + 1); }; return ( <p> {laskuri} <button type="button" onClick={kasvata}>Kasvata</button> </p> ); }; - DOM-rajapinnan kautta html-elementteihin ei yleensä tarvitse päästä käsiksi. Lomakkeiden kenttiin pääsee tapahtumankäsittelijässä event.target-ominaisuuden kautta. Kts. Manipulating the DOM with refs.
- Luokkakomponentteja näkee yhä vanhoissa projekteissa. Kts. Component — uutta koodia niillä ei kannata kirjoittaa.
React ja silmukat
JSX-koodiin voi kirjoittaa vain React-elementtejä, html-elementtejä sekä aaltosulkeiden sisään lausekkeita: funktiokutsuja, laskutoimituksia ja muuttujien nimiä. for-silmukka on lause eikä lauseke, joten se on suoritettava erikseen ennen palautusta. Sisäkkäisistä silmukoista sisempi siirretään omaan funktioonsa tai komponenttiinsa.
const Test = function () {
const taulukko = ["foo", "bar", "foobar", "barfoo"];
const taulukko2 = [1, 2, 3, 4];
let aputaulukko = [];
// JSX-koodissa voi olla vain lausekkeita, ja taulukko[i] on lauseke.
// Muista antaa jokaiselle riville uniikki key-attribuutti.
for (let i = 0; i < taulukko.length; i++) {
aputaulukko.push(<li key={taulukko[i]}>{taulukko[i]}</li>);
}
aputaulukko = [];
// Entä jos silmukoita on kaksi? Sisempi siirretään omaan komponenttiinsa.
for (const ulompi of taulukko2) {
aputaulukko.push(<Apukomponentti key={ulompi} taulukko={taulukko} ulompi={ulompi} />);
}
return (
<ul>
{aputaulukko}
</ul>
);
/* Tämä EI toimi, vaan antaa unexpected token -virheen: for on lause, ei lauseke.
return (
<ul>{
for (let i = 0; i < taulukko.length; i++) {
aputaulukko.push(<li>{taulukko[i]}</li>);
}
}</ul>
);
*/
};
const Apukomponentti = function (props) {
const aputaulukko = [];
for (const alkio of props.taulukko) {
aputaulukko.push(<li key={alkio}>{alkio}</li>);
}
return (
<li>
{props.ulompi}
<ul>
{aputaulukko}
</ul>
</li>
);
};
Käytännössä silmukka kirjoitetaan yleensä map-metodilla, joka on lauseke ja mahtuu siksi suoraan JSX:n sisään:
return (
<ul>
{taulukko.map((alkio) => <li key={alkio}>{alkio}</li>)}
</ul>
);
Älä käytä silmukan indeksiä key-arvona, jos listan järjestys voi muuttua tai siitä voidaan poistaa alkioita. Avaimen tehtävä on kertoa Reactille mikä rivi on mikäkin, ja indeksi muuttuu järjestyksen mukana. Kts. Rendering lists.
Lomakkeet
Reactissa lomakkeet ovat tavallisesti kontrolloituja: kentän arvo tulee komponentin tilasta, ja jokainen muutos päivittää tilan. Käytä kontrolloitua lomaketta aina kun mahdollista. Joissakin tilanteissa lomakkeesta voi tehdä myös kontrolloimattoman, jolloin arvot luetaan vasta lähetettäessä.
Lomakkeissa voi käyttää samoja tarkistuksia ja virheilmoituksia kuin muutenkin. Kts. validointiesimerkki.
Komponentin tila
- props sisältää komponentille attribuutteina tuodut parametrit. Niiden arvoja ei muuteta komponentissa.
- Tilamuuttujat luodaan useState-kutsulla. Niitä saa ja pitää
muuttaa, mutta vain useState-kutsun palauttamalla
asetusfunktiolla. Tilamuuttujia voi olla useita ja niiden nimet voivat
olla mitä tahansa, mutta tapana on nimetä asetusfunktio muodossa
setMuuttuja. Kun tila muuttuu, React renderöi komponentin
uudelleen.
setState on luokkakomponenttien metodi. Funktiokomponenteissa sitä ei ole.
- Objekteja ja taulukoita ei muuteta paikallaan vaan niistä tehdään uusi kopio.
Helpoimmin se käy
spread-syntaksilla:
const [tila, setTila] = useState({ nimi: "Matti", osoite: "Kotikatu 1", numerot: [1, 2, 3] }); // ...tila tekee pinnallisen kopion, ja seuraava rivi korvaa yhden avaimen arvon. // Tässä riittää pinnallinen kopio, koska vain osoite muuttuu. Jos muuttaisimme // numerot-taulukkoa, sekin pitäisi kopioida erikseen. setTila({ ...tila, osoite: "Uusikatu 2" });
Tila ei päivity heti
Huom. Tilamuuttuja on kyseisen renderöinnin ajan vakio. Sen arvo ei muutu asetusfunktion kutsumisesta, vaan vasta seuraavassa renderöinnissä. Seuraava tulostaa siis vanhan arvon:
setLaskuri(laskuri + 1);
console.log(laskuri); // vanha arvo, ei kasvatettu
Samasta syystä peräkkäiset kasvatukset eivät kerry:
setLaskuri(laskuri + 1);
setLaskuri(laskuri + 1); // molemmat laskevat samasta vanhasta arvosta
// -> laskuri kasvaa yhdellä, ei kahdella
Jos uusi arvo riippuu edellisestä, anna asetusfunktiolle funktio:
setLaskuri((edellinen) => edellinen + 1);
setLaskuri((edellinen) => edellinen + 1); // nyt kasvaa kahdella
Kts. State as a snapshot ja Queueing a series of state updates.
Monimutkainen tila
Huom. Jos tallennat tilaan objekteja tai taulukoita, ole tarkkana kopioinnissa. Esimerkki muuttuvasta ja muuttumattomasta tilasta (mutable.js) näyttää konkreettisesti, mitä tapahtuu kun tilaa muutetaan paikallaan: alkio menee tilaan mutta ei ilmesty näkyviin.
Kts. myös Updating objects in state ja Updating arrays in state.
Jos tarvitset komponentissa muuttujia, jotka eivät vaikuta renderöintiin, niitä ei tarvitse säilyttää tilassa. Tavallinen muuttuja riittää — tai useRef, jos arvon on säilyttävä renderöintien yli.
Fetch ja useEffect
- fetch-esimerkki (fetch.js) — .then-syntaksilla
- sama await-syntaksilla (fetch2.js)
Perusteellisempi käsittely löytyy sivulta React ja koukut.
Käyttäjien kommentit