Interaktiivisuuden lisääminen

Jotkin asiat ruudulla päivittyvät käyttäjän syötteen mukaan. Esimerkiksi, kuvan klikkaaminen galleriassa vaihtaa aktiivista kuvaa. Reactissa ajan myötä muuttuvia tietoja kutsutaan tilaksi. Voit lisätä tilan mihin tahansa komponenttiin ja päivittää sitä tarvittaessa. Tässä luvussa opit miten kirjoitetaan komponentteja, jotka vastaavat toimintohin, päivittävät niiden tilaa sekä näyttävät eri tulostetta ajan myötä.

Vastaaminen tapahtumiin

Reactissa voit lisätä Tapahtumankäsittelijöitä JSX koodiin. Tapahtumankäsittelijät ovat funktioitasi, joita kutsutaan vastauksena käyttäjän toimintoihin kuten klikkaukseen, hoverointiin, focusointiin ja niin edelleen.

Sisäänrakennetut komponentit kuten <button> tukevat ainoastaan selaimen sisäänrakennettuja tapahtumia kuten onClick. Voit kuitenkin luoda omia komponentteja ja niiden Tapahtumankäsittelijöiden nimet voivat olla niin sovelluskohtaisia kuin haluat.

export default function App() {
  return (
    <Toolbar
      onPlayMovie={() => alert('Playing!')}
      onUploadImage={() => alert('Uploading!')}
    />
  );
}

function Toolbar({onPlayMovie, onUploadImage}) {
  return (
    <div>
      <Button onClick={onPlayMovie}>Play Movie</Button>
      <Button onClick={onUploadImage}>Upload Image</Button>
    </div>
  );
}

function Button({onClick, children}) {
  return <button onClick={onClick}>{children}</button>;
}

Oletko valmis oppimaan tämän aiheen?

Lue Responding to Events oppiaksesi miten lisätä Tapahtumankäsittelijöitä.

Lue lisää

Tila: komponentin muisti

Komponenttien on usein muutettava näytön sisältöä vuorovaikutuksen seurauksena. Lomakkeeseen kirjoittaminen päivittää syöttökenttää, kuvakarusellissa “seuraava” napsauttaminen tulisi muuttaa näytettävää kuvaa, “osta”-painike asettaa tuotteen ostoskoriin. Komponenttien täytyy “muistaa” asioita: nykyinen syöte, nykyinen kuva, ostoskori. Reactissa tämän kaltaista komponenttikohtaista muistia kutsutaan tilaksi.

Voit lisätä komponentteihin tilan käyttämällä useState koukkua eli hookkia. Hookit ovat erikoisfunktioita, joilla voit käyttää Reactin ominaisuuksia komponenteissasi (tila on yksi näistä ominaisuuksista). useState hookilla voit määritellä tilamuuttujan. Sille voidaan antaa alustava tila ja se palauttaa parin arvoja: nykyisen tilan, sekä setter funktion, jolla voit päivittää sitä.

const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);

Tässä näet miten kuvagalleria käyttää käyttää ja päivittää tilaa napsautuksella:

import {useState} from 'react';
import {sculptureList} from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);
  const hasNext = index < sculptureList.length - 1;

  function handleNextClick() {
    if (hasNext) {
      setIndex(index + 1);
    } else {
      setIndex(0);
    }
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleNextClick}>Next</button>
      <h2>
        <i>{sculpture.name} </i>
        by {sculpture.artist}
      </h2>
      <h3>
        ({index + 1} of {sculptureList.length})
      </h3>
      <button onClick={handleMoreClick}>
        {showMore ? 'Hide' : 'Show'} details
      </button>
      {showMore && <p>{sculpture.description}</p>}
      <img src={sculpture.url} alt={sculpture.alt} />
    </>
  );
}

Oletko valmis oppimaan tämän aiheen?

Lue Tila: Komponentin muisti oppiaksesi miten arvo muistetaan ja päivitetään vuorovaikutuksen vuoksi.

Lue lisää

Renderöinti ja kommitointi

Ennen kuin komponenttisi näytetään ruudlla, Reactin täytyy renderöidä ne. Tämän prosessin vaiheiden ymmärtäminen auttaa sinua miettimään miten koodiasi suoritetaan ja selittämään sen toimintaa.

Kuvittele, että komponettisi ovat kokkeja keittiössä, kasaten maukkaita aterioita ainesosista. Tässä skenaariossa React on tarjoilija joka laittaa ylös asiakkaiden tilaukset sekä vie tilaukset heille. Tässä käyttöliittymän pyyntö- ja vientiprosessissa on kolme vaihetta:

  1. Käynnistetään renderöinti (viedään ruokalijan tilaus keittiölle)
  2. Renderöidään komponentti (valmistellaan tilausta keittiössä)
  3. Kommitoidaan DOM:iin (asetetaan tilaus pöydälle)
  1. React toimii tarjoilijana ravintolassa, hakien tilauksia käyttäjiltä ja tarjoillen niitä Keittiö komponentille.
    Käynnistys
  2. Card kokki antaa Reactille tuoreen Card komponentin.
    Renderöinti
  3. React tarjoaa Card komponentin käyttäjälle heidän pöydälleen.
    Kommitointi

Illustrated by Rachel Lee Nabors

Oletko valmis oppimaan tämän aiheen?

Lue Render and Commit oppiaksesi käyttöliittymäpäivityksen elämänkaaren.

Lue lisää

Tila tilannekuvana

Toisin kuin tavalliset JavaScript muuttujat, Reactin tila käyttäytyy enemmän kuin tilannekuva. Tilan muuttaminen ei muuta tilamuuttujaa joka sinulla jo on, vaan sen sijaan käynnistää uudelleenrenderöinnin. Tämä saattaa olla yllättävää aluksi!

console.log(count); // 0
setCount(count + 1); // Pyydä uudelleenrenderöinti arvolla 1
console.log(count); // Silti 0!

Tämä käyttäytyminen helpottaa välttämään hienovaraisia bugeja. Tässä on pieni chätti-sovellus. Veikkaa mitä tapahtuu kun painat ensin “Lähetä” ja sitten muutat vastaanottajan Jaakoksi. Kenen nimi tulee näkymään alert:ssa viiden sekuntin jälkeen?

import {useState} from 'react';

export default function Form() {
  const [to, setTo] = useState('Liisa');
  const [message, setMessage] = useState('Hei');

  function handleSubmit(e) {
    e.preventDefault();
    setTimeout(() => {
      alert(`Sanoit ${message} henkilölle ${to}`);
    }, 5000);
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Henkilölle:{' '}
        <select value={to} onChange={(e) => setTo(e.target.value)}>
          <option value="Liisa">Liisa</option>
          <option value="Jaakko">Jaakko</option>
        </select>
      </label>
      <textarea
        placeholder="Viesti"
        value={message}
        onChange={(e) => setMessage(e.target.value)}
      />
      <button type="submit">Lähetä</button>
    </form>
  );
}

Oletko valmis oppimaan tämän aiheen?

Lue Tila tilannekuvana oppiaksesi miksi tila vaikuttaa “vakiolta” ja muuttumattomalta Tapahtumankäsittelijöissä.

Lue lisää

Tilapäivityksen lisääminen jonoon

Tämä komponentti on buginen: kun klikataan “+3” se nostaa pisteytystä vain kerran.

import {useState} from 'react';

export default function Counter() {
  const [score, setScore] = useState(0);

  function increment() {
    setScore(score + 1);
  }

  return (
    <>
      <button onClick={() => increment()}>+1</button>
      <button
        onClick={() => {
          increment();
          increment();
          increment();
        }}>
        +3
      </button>
      <h1>Score: {score}</h1>
    </>
  );
}

Tila tilannekuvana selittää miksi näin tapahtuu. Tilan asettaminen pyytää uudelleenrenderöinnin, mutta ei muuta sitä jo suoritettavassa koodissa. Joten score jää arvoksi 0 heti sen jälkeen kun kutsut setScore(score + 1).

console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0

Voit korjata tämän välittämällä päivitysfunktion tilamuutoksen yhteydessä. Huomaa miten setScore(score + 1) muuttaminen setScore(s => s + 1) funktioksi korjaa “+3” painikkeen. Tämän avulla voit lisätä useita tilamuutoksia jonoon.

import {useState} from 'react';

export default function Counter() {
  const [score, setScore] = useState(0);

  function increment() {
    setScore((s) => s + 1);
  }

  return (
    <>
      <button onClick={() => increment()}>+1</button>
      <button
        onClick={() => {
          increment();
          increment();
          increment();
        }}>
        +3
      </button>
      <h1>Score: {score}</h1>
    </>
  );
}

Oletko valmis oppimaan tämän aiheen?

Lue Tilapäivityksen lisääminen jonoon oppiaksesi miten lisätään useita tilapäivityksiä jonoon.

Lue lisää

Olioiden päivittäminen tilassa

Tila voi pitää sisällään mitä vain JavaScript arvoa, kuten olioita. Mutta React tilassa olevia olioita ja listoja ei pitäisi muuttaa suoraan. Sen sijaan kun haluat päivittää oliota ja listaa, luo uusi (tai tee kopio olemassa olevasta), ja sitten päivitä tila käyttämään uutta kopiota.

Yleensä käytät ... levityssyntaksia kopioidaksesi oliot ja listat joita haluat muuttaa. Esimerkiksi, sisäkkkäisen olion päivittäminen voisi näyttää seuraavalta:

import {useState} from 'react';

export default function Form() {
  const [person, setPerson] = useState({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      image: 'https://i.imgur.com/Sd1AgUOm.jpg',
    },
  });

  function handleNameChange(e) {
    setPerson({
      ...person,
      name: e.target.value,
    });
  }

  function handleTitleChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        title: e.target.value,
      },
    });
  }

  function handleCityChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        city: e.target.value,
      },
    });
  }

  function handleImageChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        image: e.target.value,
      },
    });
  }

  return (
    <>
      <label>
        Name:
        <input value={person.name} onChange={handleNameChange} />
      </label>
      <label>
        Title:
        <input value={person.artwork.title} onChange={handleTitleChange} />
      </label>
      <label>
        City:
        <input value={person.artwork.city} onChange={handleCityChange} />
      </label>
      <label>
        Image:
        <input value={person.artwork.image} onChange={handleImageChange} />
      </label>
      <p>
        <i>{person.artwork.title}</i>
        {' by '}
        {person.name}
        <br />
        (located in {person.artwork.city})
      </p>
      <img src={person.artwork.image} alt={person.artwork.title} />
    </>
  );
}

Jos olioiden kopiointi käy tylsäksi, voit käyttää kirjastoa kuten Immer vähentääksesi toistettavaa koodia:

import {useImmer} from 'use-immer';

export default function Form() {
  const [person, updatePerson] = useImmer({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      image: 'https://i.imgur.com/Sd1AgUOm.jpg',
    },
  });

  function handleNameChange(e) {
    updatePerson((draft) => {
      draft.name = e.target.value;
    });
  }

  function handleTitleChange(e) {
    updatePerson((draft) => {
      draft.artwork.title = e.target.value;
    });
  }

  function handleCityChange(e) {
    updatePerson((draft) => {
      draft.artwork.city = e.target.value;
    });
  }

  function handleImageChange(e) {
    updatePerson((draft) => {
      draft.artwork.image = e.target.value;
    });
  }

  return (
    <>
      <label>
        Name:
        <input value={person.name} onChange={handleNameChange} />
      </label>
      <label>
        Title:
        <input value={person.artwork.title} onChange={handleTitleChange} />
      </label>
      <label>
        City:
        <input value={person.artwork.city} onChange={handleCityChange} />
      </label>
      <label>
        Image:
        <input value={person.artwork.image} onChange={handleImageChange} />
      </label>
      <p>
        <i>{person.artwork.title}</i>
        {' by '}
        {person.name}
        <br />
        (located in {person.artwork.city})
      </p>
      <img src={person.artwork.image} alt={person.artwork.title} />
    </>
  );
}

Oletko valmis oppimaan tämän aiheen?

Lue Updating Objects in State oppiaksesi miten olioita voidaan päivittää oikeaoppisesti.

Lue lisää

Listojen päivittäminen tilassa

Listat ovat toisen tyyppisiä muuttuvia JavaScript olioita, joita voit tallettaa tilaan, ja joita tulisi käsitellä vain-luku muotoisina. Juuri kuten olioiden kanssa, kun haluat päivittää listaa tilassa, tarvitset uuden listan (tai kopion olemassa olevasta), ja sitten aseta tila käyttämään uutta listaa:

import {useState} from 'react';

let nextId = 3;
const initialList = [
  {id: 0, title: 'Big Bellies', seen: false},
  {id: 1, title: 'Lunar Landscape', seen: false},
  {id: 2, title: 'Terracotta Army', seen: true},
];

export default function BucketList() {
  const [list, setList] = useState(initialList);

  function handleToggle(artworkId, nextSeen) {
    setList(
      list.map((artwork) => {
        if (artwork.id === artworkId) {
          return {...artwork, seen: nextSeen};
        } else {
          return artwork;
        }
      })
    );
  }

  return (
    <>
      <h1>Art Bucket List</h1>
      <h2>My list of art to see:</h2>
      <ItemList artworks={list} onToggle={handleToggle} />
    </>
  );
}

function ItemList({artworks, onToggle}) {
  return (
    <ul>
      {artworks.map((artwork) => (
        <li key={artwork.id}>
          <label>
            <input
              type="checkbox"
              checked={artwork.seen}
              onChange={(e) => {
                onToggle(artwork.id, e.target.checked);
              }}
            />
            {artwork.title}
          </label>
        </li>
      ))}
    </ul>
  );
}

Jos olioiden kopiointi käy tylsäksi, voit käyttää kirjastoa kuten Immer vähentääksesi toistettavaa koodia:

import {useState} from 'react';
import {useImmer} from 'use-immer';

let nextId = 3;
const initialList = [
  {id: 0, title: 'Big Bellies', seen: false},
  {id: 1, title: 'Lunar Landscape', seen: false},
  {id: 2, title: 'Terracotta Army', seen: true},
];

export default function BucketList() {
  const [list, updateList] = useImmer(initialList);

  function handleToggle(artworkId, nextSeen) {
    updateList((draft) => {
      const artwork = draft.find((a) => a.id === artworkId);
      artwork.seen = nextSeen;
    });
  }

  return (
    <>
      <h1>Art Bucket List</h1>
      <h2>My list of art to see:</h2>
      <ItemList artworks={list} onToggle={handleToggle} />
    </>
  );
}

function ItemList({artworks, onToggle}) {
  return (
    <ul>
      {artworks.map((artwork) => (
        <li key={artwork.id}>
          <label>
            <input
              type="checkbox"
              checked={artwork.seen}
              onChange={(e) => {
                onToggle(artwork.id, e.target.checked);
              }}
            />
            {artwork.title}
          </label>
        </li>
      ))}
    </ul>
  );
}

Oletko valmis oppimaan tämän aiheen?

Lue Updating Arrays in State oppiaksesi miten listoja päivitetään oikeaoppisesti.

Lue lisää

Mitä seuraavaksi?

Siirry seuraavaksi Vastaaminen tapahtumiin -sivulle lukeaksesi tämän luvun sivu kerrallaan!

Tai, jos aiheet ovat jo tuttuja, mikset lukisi Tilan hallintaa?