Skip to content

Reactin perusteet

  • React on avoimen lähdekoodin JavaScript-kirjasto, joka on suunniteltu käyttöliittymien rakentamiseen
  • Kehittäjä Facebook
  • Tällä hetkellä eniten työelämässä käytetty javaScript-pohjainen sovelluskehys/kirjasto (StackOverflow Developer Survey 2023)

Linkki Reactin omaan dokumentaatioon: https://react.dev/

Käyttöönotto

Varmista että nodejs on asennettu

  1. Avaa komentorivi ja navigoi sillä kansioon minne haluat luoda uuden React projektin.

Käytetään vite pakkaajaa sen nopeuden ja helppokäyttöisyyden vuoksi

  1. Suorita komentorivillä seuraava komento:
sh
npm create vite@latest
npm create vite@latest
  1. Jos komenrivi pyytää lupaa asentaa viten paketit, anna lupa.

  2. Seuraavaksi anna projektille nimi, tämä määrittää käytännössä vain kansion nimen, minne vite tulee alustamaan uuden React projektin.

  3. Valitse framework listauksesta React ja tämän jälkeen ohjelmointikieleksi javaScript (tai javaScript + SWC)

  4. Suorita komentorivin neuvomat komennot "cd <kansion_nimi>" ja "npm install", näiden jälkeen voit käynnistää kehityspalvelimen komennolla "npm run dev"

Esimerkki ToDo-sovelluksesta Reactilla

Avaa uuteen ikkunaan

JSX

JSX:n ja HTML:n eroavaisuudet Reactissa:

Eroavaisuudet

Tagien sulkeminen: JSX:ssä kaikkien elementtien on oltava suljettuja, myös itsestään sulkeutuvat elementit, kuten <br /> ja <img />.

JavaScript-ilmaisut: JSX:ssä voit upottaa JavaScript-ilmaisuja käyttämällä aaltosulkeita {}.

Ominaisuudet: Joitakin HTML-ominaisuuksia kirjoitetaan eri tavalla JSX:ssä. Esimerkiksi, class on className, ja tabindex on tabIndex.

Tapahtumankuuntelijat: JSX:ssä tapahtumankuuntelijat, kuten onClick, kirjoitetaan camelCase-muodossa.

Esimerkki JSX:n Käytöstä

javascript
function MyComponent() {
  return (
    <div className="myComponent">
      <h1>Hei, maailma!</h1>
      <button onClick={() => console.log('Nappia painettu')}>Olen nappi</button>
    </div>
  );
}
function MyComponent() {
  return (
    <div className="myComponent">
      <h1>Hei, maailma!</h1>
      <button onClick={() => console.log('Nappia painettu')}>Olen nappi</button>
    </div>
  );
}

Ilman JSX:ää

React-komponentti voidaan kirjoittaa myös ilman JSX:ää käyttämällä React.createElement -funktiota.

Esimerkki ilman JSX:ää

javascript
function MyComponent() {
  return React.createElement(
    'div', 
    { className: 'myComponent' },
    React.createElement('h1', null, 'Hei, maailma!'),
    React.createElement('button', { onClick: () => console.log('Nappia painettu') }, 'Olen nappi')
  );
}
function MyComponent() {
  return React.createElement(
    'div', 
    { className: 'myComponent' },
    React.createElement('h1', null, 'Hei, maailma!'),
    React.createElement('button', { onClick: () => console.log('Nappia painettu') }, 'Olen nappi')
  );
}

JSX tarjoaa selkeämmän ja tiiviimmän syntaksin, tämä helpottaa koodin lukemista ja vähentää myös tehtävän koodin määrää.

Tyylit

CSS:n asettaminen JSX:ssä voidaan tehdä useilla eri tavoilla.

Ulkoinen CSS-Tiedosto

Tämä on perinteinen tapa, jossa CSS säännöt kirjoitetaan erilliseen tiedostoon.

jsx
// Tuodaan css tiedosto 
import "./App.css"

// Komponentti
function App() {
  return <div className="app">Hei maailma!</div>;
}

// Ulkoinen CSS-tiedosto
// App.css
.app {
  color: blue;
}
// Tuodaan css tiedosto 
import "./App.css"

// Komponentti
function App() {
  return <div className="app">Hei maailma!</div>;
}

// Ulkoinen CSS-tiedosto
// App.css
.app {
  color: blue;
}

Inline-tyylit

Voit asettaa tyylejä suoraan JSX-elementteihin käyttämällä style-attribuuttia, joka ottaa vastaan JavaScript-objektin. Huomaa että css-atribuutit ovat tällöin javaScript muodossa, joten kaksi-osaiset css atribuutit kirjoitetaan käyttämällä pascalCasea. Esim. border-bottom-right kirjoitetaan muodossa borderBottomRight kun käytetään inline tyyleja.

jsx
function App() {
  return <div style={{ color: 'blue' }}>Hei maailma!</div>;
}
function App() {
  return <div style={{ color: 'blue' }}>Hei maailma!</div>;
}

CSS-moduulit

CSS-moduulit mahdollistavat CSS:n kapseloinnin yksittäisiin komponentteihin.

jsx
import styles from './App.module.css';

function App() {
  return <div className={styles.app}>Hei maailma!</div>;
}

// CSS-moduuli
// App.module.css
.app {
  color: blue;
}
import styles from './App.module.css';

function App() {
  return <div className={styles.app}>Hei maailma!</div>;
}

// CSS-moduuli
// App.module.css
.app {
  color: blue;
}

Styled-components

styled-components on kirjasto, joka mahdollistaa CSS:n kirjoittamisen suoraan JavaScript-tiedostoihin komponenttien yhteyteen.

styled-components dokumentaatio

jsx
import styled from 'styled-components';

// Luodaan esimerkiksi div-elementti, minkä sisällä teksti on sinistä seuraavasti
const StyledDiv = styled.div`
  color: blue;
`;

// Käytetään styled komponentteja kuten 
function App() {
  return <StyledDiv>Hei maailma!</StyledDiv>;
}
import styled from 'styled-components';

// Luodaan esimerkiksi div-elementti, minkä sisällä teksti on sinistä seuraavasti
const StyledDiv = styled.div`
  color: blue;
`;

// Käytetään styled komponentteja kuten 
function App() {
  return <StyledDiv>Hei maailma!</StyledDiv>;
}

Reactin dokumentaatiossa lisää tyylittelystä: https://react.dev/learn#adding-styles

Komponentit ja Propsit(Props)

Funktionaaliset Komponentit

Funktionaaliset komponentit ovat kirjaimellisesti javaScript funktioita. Taustalla React muuttaa JSX koodin javaScript muotoon.

Funktionaaliset komponentit toimivat kuten javaScript-funktiot, esim. propsien ovat vain argumentteja joita funktio saa sieltä missä sitä on kutsuttu.

Komponentit eivät oletuksena tiedä toistensa tilasta automaattisesti mitään, kuten eivät javaScript-funktiotkaan:

js
// JavaScript funktio
function helloWorld(argument){
    return "Hello " + argument
}

console.log(helloWorld("World"))        // "Hello World"
console.log(helloWorld("Full Stack!"))  // "Hello Full Stack!"
// JavaScript funktio
function helloWorld(argument){
    return "Hello " + argument
}

console.log(helloWorld("World"))        // "Hello World"
console.log(helloWorld("Full Stack!"))  // "Hello Full Stack!"
jsx
// React komponentti
function HelloWorld({argument}){
    return <p> "Hello " {argument} </p>
}
// Jossain JSX koodissa:
<HelloWorld argument="World"></HelloWorld>          // React.createElement('p', null, 'Hello World')
<HelloWorld argument="Full Stack!"></HelloWorld>    // React.createElement('p', null, 'Hello Full Stack!')
// React komponentti
function HelloWorld({argument}){
    return <p> "Hello " {argument} </p>
}
// Jossain JSX koodissa:
<HelloWorld argument="World"></HelloWorld>          // React.createElement('p', null, 'Hello World')
<HelloWorld argument="Full Stack!"></HelloWorld>    // React.createElement('p', null, 'Hello Full Stack!')

Esimerkki: Funktionaalinen Komponentti

javascript
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

Propsit (Props)

Propsit ovat tietoja joilla voidaan välittää tietoja parent-komponentilta child-komponentille. Propseissa voidaan välittää mitä tahansa javaScript tietotyyppiä, kuten string, number, array, object tai function. JavaScriptin tietotyypeistä lisää MDN: dokumentaatiossa.

Propsien Välittämisen Eri Tavat

Suora Välitys Propsit välitetään suoraan komponentille.

javascript
<Welcome name="Sara" />
<Welcome name="Sara" />

Destructurointi Komponentissa Funktionaaliset komponentit saavat yhden objektin parametrina, mistä käytetään nimeä props. Se sisältää kaikki tiedot avain-arvo pareina mitkä parent-komponentissa on child-komponentille määritetty (ks. kohta 1).

javascript
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

Destructurointi Komponentissa Propsien destructurointi komponentin määrittelyssä. Tämä on yleisesti käytetty tapa ja mahdollistaa sen että komponentista näkee heti funktion argumenttien kohdalla mitä dataa komponentti saa parent-komponentilta.

javascript
function Welcome({ name }) {
  return <h1>Hello, {name}</h1>;
}
function Welcome({ name }) {
  return <h1>Hello, {name}</h1>;
}

Koko Props-objektin Välittäminen Välitetään koko props-objekti toiselle komponentille.

javascript
function App() {
  const welcomeProps = { name: "Sara" };
  return <Welcome {...welcomeProps} />;
}
function App() {
  const welcomeProps = { name: "Sara" };
  return <Welcome {...welcomeProps} />;
}

Erityiset Propsit: children

props.children on erityinen propsi, joka sisältää kaiken komponentin tägien sisällä olevan. Alapuolen esimerkissä <h1> tägillä on mm. pääsy ParentComponentin tilaan vaikka se renderoityy ChildComponent-komponentin sisällä.

jsx
function ChildComponent({children}){
    return <div>{children}</div>
}

function ParentComponent() {
    return <>
        <ChildComponent>
            <h1> Tämä h1-tägi renderöityy child componentissa siellä missä "children" on asetettu tägien väliin <h1>
        </ChildComponent>
    </>
}
function ChildComponent({children}){
    return <div>{children}</div>
}

function ParentComponent() {
    return <>
        <ChildComponent>
            <h1> Tämä h1-tägi renderöityy child componentissa siellä missä "children" on asetettu tägien väliin <h1>
        </ChildComponent>
    </>
}

Nimeämiskäytäntö

React-komponenttien nimet alkavat tyypillisesti isolla alkukirjaimella. Tämä eroaa tavallisista HTML-tageista, jotka ovat pienellä kirjaimella. Esimerkiksi koodieditorit, kuten VS Code, osaavat päätellä että kyseessä on tällöin React-komponentti kun function nimi alkaa isolla alkukirjaimella .jsx tiedostossa.

Tilanhallinta, useState hook

Nimeämissääntö

Tilamuuttujat nimetään yleensä muodossa [muuttuja, setMuuttuja] käyttäen JavaScriptin Desctructuring assignment menetelmää.

useState(initialState)

Käytä useState-hookia komponenttisi ylimmällä tasolla tilamuuttujien määrittämiseksi.

javascript
import { useState } from 'react';

function MyComponent() {
  // [state, setState] = useState(initialState)  
  const [age, setAge] = useState(28);
  const [name, setName] = useState('Taylor');
  const [todos, setTodos] = useState(() => createTodos());
}
import { useState } from 'react';

function MyComponent() {
  // [state, setState] = useState(initialState)  
  const [age, setAge] = useState(28);
  const [name, setName] = useState('Taylor');
  const [todos, setTodos] = useState(() => createTodos());
}

Parametrit

  • initialState: Tilan alkuarvo, voi olla minkä tahansa tyyppinen.

Palautusarvot

useState palauttaa kaksi arvoa:

  1. Nykyinen tila (state).
  2. Tilan päivitysfunktio eli set-funktio (setState).

set-funktiot

set-funktio päivittää tilan ja laukaisee komponentin uudelleenrenderöinnin.

Parametrit

  • nextState: Uusi tilan arvo.

Palautusarvot

set-funktiolla ei ole palautusarvoa.

Tilan muuttaminen komponentissa

  1. Napin painaminen suorittaa onClick-tapahtumankuuntelijalle annetun funktion handleClick
  2. handleClick funktiossa oleva setName() set-funktio suorittuu, mikä laukaisee komponentin uudelleen renderöinnin ja vaihtaa 'Pekka' nimen komponentin tilassa 'Pätkä' nimeksi
javascript
function MyComponent() {
    const [name, setName] = useState('Pekka');

    function handleClick() {
        setName('Pätkä');
    }
    
    return <>
        <p>Nimi: {name} </p>
        <button onClick={handleClick}> Nappi <button>
    </>
}
function MyComponent() {
    const [name, setName] = useState('Pekka');

    function handleClick() {
        setName('Pätkä');
    }
    
    return <>
        <p>Nimi: {name} </p>
        <button onClick={handleClick}> Nappi <button>
    </>
}

React Hooks

Reactin hookit ovat olleet mukana Reactin versiosta 16.8 lähtien. Ne mahdollistavat tilan ja muiden React-ominaisuuksien käytön ilman luokkapohjaisten komponenttien tarvetta. Hookkien avulla voidaan hallita komponentin tilaa ja sivuvaikutuksia funktionaalisissa komponenteissa.

Uudelleenkäyttö yksinkertaistaa monimutkaisia komponentteja

Hookit mahdollistavat komponentin tilaa käyttävien toiminnallisuuksien irrottamisen komponentin koodista, näin ollen tätä komponentista erotettua logiikkaa (koodia) voidaan testata erillään sekä käyttää uudelleen muissa komponenteissa.

Hook kiinnittyy aina sen komponentin tilaan sillä hetkellä missä sitä milloinkin käytetään ja sen sisältämä data on tällöin kiinni komponentin elinkaaressa.

Poikkeuksena tähän ovat Hookit, jotka ovat tarkoitettu nimenomaisesti komponentin ulkopuoliseen tilanhallintaan (esim. useContext) tai tiedon välittämiseen selaimen eri rajapintoihin (esim. useLocalStorage).

Hookien Käyttö

Katsotaan esimerkin kautta kuinka komponentin toimintalogiikkaa voidaan eristää uudelleen käytettävään Hookiin.

Esimerkin komponentissa on yksinkertainen toggle-toiminnallisuus toteutettu käyttämällä useState-hookia.

Huomataan että tämä toiminnallisuus voisi todennäköisesti olla sellainen jota voisi hyödyntää myös muissa komponenteissa.

jsx
import React, { useState } from 'react';

function ExampleComponent() {
  const [value, setValue] = useState(false);

  // Tämä funktio kääntää totuusarvon tilan
  function toggleValue() {
    setValue(currentValue => !currentValue);
  }

  return (
    <div>
      <p>Tila on: {value.toString()}</p>
      <button onClick={toggleValue}>Vaihda tilaa</button>
    </div>
  );
}
import React, { useState } from 'react';

function ExampleComponent() {
  const [value, setValue] = useState(false);

  // Tämä funktio kääntää totuusarvon tilan
  function toggleValue() {
    setValue(currentValue => !currentValue);
  }

  return (
    <div>
      <p>Tila on: {value.toString()}</p>
      <button onClick={toggleValue}>Vaihda tilaa</button>
    </div>
  );
}

Jotta koodin toistamiselta vältytään, eristetään toggle-toiminnon logiikka omaan räätälöityyn hookiin useToggle.

Hook on siis käytännössä tavallinen javaScript funktio, joka sisältää React-komponentin tilanhallintaan liittyvää koodia.

jsx
import { useState } from 'react';

function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);

  // Tämä funktio kääntää totuusarvon tilan
  function toggleValue() {
    setValue(currentValue => !currentValue);
  }

  return [value, toggleValue];
}

export default useToggle;
import { useState } from 'react';

function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);

  // Tämä funktio kääntää totuusarvon tilan
  function toggleValue() {
    setValue(currentValue => !currentValue);
  }

  return [value, toggleValue];
}

export default useToggle;

Nyt toggle-toiminnan logiikka on eristetty omaan funktioon eli hookkiin.

Kun tämä useToggle funktio suoritetaan komponentin ylätasolla, sen sisältämä useState() funktio suorittuu ja näin hookki kiinnittyy komponentin sen hetkiseen tilaan.

jsx
import React from 'react';
import useToggle from './useToggle';

function ExampleComponent() {
  const [value, toggleValue] = useToggle();

  return (
    <div>
      <p>Tila on: {value.toString()}</p>
      <button onClick={toggleValue}>Vaihda tilaa</button>
    </div>
  );
}
import React from 'react';
import useToggle from './useToggle';

function ExampleComponent() {
  const [value, toggleValue] = useToggle();

  return (
    <div>
      <p>Tila on: {value.toString()}</p>
      <button onClick={toggleValue}>Vaihda tilaa</button>
    </div>
  );
}

Hookin koodissa täytyy huolehtia tilan siivoamisesta (kuten myös komponenteissa!), jos hookissa on käytetty sivuvaikutuksia, esim. selaimen natiivi-tapahtumankuuntelijoita:

jsx
import React, { useEffect } from 'react';

function useResize() {

  // Asetetaan lähtöarvoksi selainikkunan leveys, kun tätä hookia käyttävä komponentti renderöityy
  const [width, setWidth] = useState(window.innerWidth)

  // Käytetään useEffect hookia tilan muutoksen seuraamiseen
  useEffect(() => {
    // Esitellään handleResize funktio lähelle tapahtumankuuntelijoita.
    function handleResize() {
        setWidth(window.innerWidth)
    }

    // Kiinnitetään handleResize funktio window-objektin tapahtumankuuntelijaan.
    // Suorittaa handleResize funktion kun selainikkunan kokoa muutetaan 
    window.addEventListener('resize', handleResize);

    // Poistaa tapahtumankuuntelijan window-objektista kun useResize hookia käyttävä komponentti "kuolee" 
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);
  
  // Paluuarvona hookia kutsuva komponentti saa reaktiivisen width-muuttujan.
  // Eli tässä tapauksessa useEffect laukaisee hookia kutsuneen komponentin 
  // uudelleen renderöinnin, kun useEffectin sisällä oleva tila muuttuu.
  return { width };
}
import React, { useEffect } from 'react';

function useResize() {

  // Asetetaan lähtöarvoksi selainikkunan leveys, kun tätä hookia käyttävä komponentti renderöityy
  const [width, setWidth] = useState(window.innerWidth)

  // Käytetään useEffect hookia tilan muutoksen seuraamiseen
  useEffect(() => {
    // Esitellään handleResize funktio lähelle tapahtumankuuntelijoita.
    function handleResize() {
        setWidth(window.innerWidth)
    }

    // Kiinnitetään handleResize funktio window-objektin tapahtumankuuntelijaan.
    // Suorittaa handleResize funktion kun selainikkunan kokoa muutetaan 
    window.addEventListener('resize', handleResize);

    // Poistaa tapahtumankuuntelijan window-objektista kun useResize hookia käyttävä komponentti "kuolee" 
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);
  
  // Paluuarvona hookia kutsuva komponentti saa reaktiivisen width-muuttujan.
  // Eli tässä tapauksessa useEffect laukaisee hookia kutsuneen komponentin 
  // uudelleen renderöinnin, kun useEffectin sisällä oleva tila muuttuu.
  return { width };
}

Yleisimmät hookit ja kirjastot

Ennen kuin rakennat oman hookin, voit katsoa esim. usehooks-sivustolta että onko joku muu sen jo tehnyt aiemmin ja säästää näin hieman aikaa ja vaivaa: https://usehooks.com/

Lapin AMK:n Full Stack opintojaksojen nettisivu.