Tila: Komponentin muisti

Components often need to change what’s on the screen as a result of an interaction. Typing into the form should update the input field, clicking “next” on an image carousel should change which image is displayed, clicking “buy” should put a product in the shopping cart. Components need to “remember” things: the current input value, the current image, the shopping cart. In React, this kind of component-specific memory is called state.

Tulet oppimaan

  • Miten lisätä tilamuuttuja useState hookilla
  • Minkä arvoparin useState hookki palauttaa
  • Miten lisätä useampi tilamuuttuja
  • Miksi tilaa sanotaan paikalliseksi

Kun tavallinen muuttuja ei riitä

Tässä on komponentti, joka renderöi kuvan veistoksesta. Klikkaamalla “Next” painiketta pitäisi seuraavan veistoksen näkyä, muuttamalla index lukua 1:een, 2:een ja niin edelleen. Kuitenkaan tämä ei toimi (voit kokeilla!):

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

export default function Gallery() {
  let index = 0;

  function handleClick() {
    index = index + 1;
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
      <p>
        {sculpture.description}
      </p>
    </>
  );
}

handleClick() tapahtumakäsittelijä päivittää paikallista index muuttujaa. Kaksi asiaa kuitenkin estävät muutoksen näkymisen:

  1. Paikalliset muuttujat eivät pysy voimassa renderien välillä. Kun React renderöi tämän komponentin toiseen kertaan, se luo sen alusta. Se ei ota huomioon paikallisten muuttujien muutoksia.
  2. Muutokset paikallisiin muuttujiin eivät käynnistä uutta renderöintiä. React ei huomaa, että sen täytyy renderöidä komponentti uudelleen uusien tietojen kanssa.

Päivittääksesi komponentti uudella datalla, kaksi asiaa täytyy tapahtua:

  1. Säilyttää data renderien välillä.
  2. Käynnistää React renderöimään komponenttin uudella datalla (uudelleenrenderöinti).

useState hookki tarjoaa molemmat näistä:

  1. Tilamuuttujan ylläpitämään data renderien välillä.
  2. Tilan asettajafunktio päivittämään muuttujaa ja käynnistämään komponentin uudelleenrenderöinti.

Tilamuuttujan lisääminen

Lisätäksesi tilamuuttuja, importtaa useState Reactista tiedoston alussa:

import { useState } from 'react';

Sitten, korvaa tämä rivi:

let index = 0;

tällä

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

index on tilamuuttuja ja setIndex on tilan asettajafunktio.

Aaltosulkeilla [ ja ] oleva syntaksi on nimeltään array destructuring ja sen avulla voit lukea arvoja listasta. useState palauttaa aina listan, jossa on kaksi kohdetta.

Tässä miten ne toimii yhdessä handleClick() funktiossa:

function handleClick() {
setIndex(index + 1);
}

Nyt klikkaamalla “Next” painiketta, kuva veistoksesta vaihtuu:

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

export default function Gallery() {
  const [index, setIndex] = useState(0);

  function handleClick() {
    setIndex(index + 1);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
      <p>
        {sculpture.description}
      </p>
    </>
  );
}

Tapaa ensimmäinen hookkisi

Reactissa useState, kuten muutkin funktiot jotka alkavat sanalla ”use”, ovat hookkeja.

Hookit ovat erityisiä funktioita, jotka ovat saatavilla vain kun React renderöi (aihe, johon perehdymme enemmän seuraavalla sivulla page). Näillä voit “koukata” erilaisiin Reactin toimintoihin.

Tila on vain yksi näistä toiminnoista, mutta tulet tapaamaan toiset hookit myöhemmin.

Sudenkuoppa

Hookit, eli use-sanalla alkavat funktiot, ovat kutsuttavissa ainoastaan komponenttisi ylätasossa tai omista hookeistasi. Et voi kutsua hookkeja ehtojen, silmukoiden tai muiden sisennettyjen funktioiden sisällä. Hookit ovat funktioita, mutta on hyödyllistä ajatella niitä kuin ehdottomina määräyksinä komponenttisi tarpeista. Sinä käytät (“use”) Reactin ominaisuuksia komponentin yläosassa samalla tavalla kuin “importtaat” moduuleja tiedoston yläosassa.

useState:n anatomia

Kun kutsut useState:a, sanot Reactille, että haluat tämän komponentin muistavan jotain:

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

Tässä tapauksessa haluat Reactin muistavan index:n.

Huomaa

Yleinen tapa on nimetä tämä pari kuten const [jotain, setJotain]. Voit nimetä sen miten haluat, mutta yleisien tapojen avulla koodia on helpompi ymmärtää projektien välillä.

Ainoa argumentti useState:lle on tilamuuttujan aloitusarvo. Tässä esimerkissä index:n aloitusarvo on 0 kun käytimme useState(0).

Joka kerta kun komponenttisi renderöityy, useState palauttaa listan sisältäen kaksi kohdetta:

  1. Tilamuuttujan (index) missä on arvo, jonka tallensit.
  2. Tilan asettajafunktion (setIndex) joka voi piäivittää tilamuuttujaa ja käynnistää komponentin uudelleenrenderöinnin.

Tässä miten se tapahtuu toiminnassa:

const [index, setIndex] = useState(0);
  1. Your component renders the first time. Koska välitit arvon 0, useState-arvon index alkuarvoksi, se palauttaa arvon [0, setIndex]. React muistaa, että 0 on viimeisin tila-arvo.
  2. Päivität tilan. Kun käyttäjä klikkaa painiketta, se kutsuu setIndex(index + 1). index on 0, joten se on setIndex(1). Tämä käskee Reactia muistamaan, että index on nyt 1 ja käynnistämään toisen renderöinnin.
  3. Komponenttisi toinen renderöinti. React näkee silti useState(0), mutta koska React muistaa, että asetit index:n aroon 1, se palauttaa [1, setIndex] kuitenkin.
  4. Ja niin edelleen!

Useiden tilamuuttujien antaminen komponentille

Voit lisätä niin monta tilamuuttujaa niin monessa eri tyypissä kuin haluat komponentin sisälle. Tällä komponentilla on kaksi tilamuuttujaa, index numero sekä showMore totuusarvo, jota vaihdetaan kun klikkaat “Show details”:

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

export default function Gallery() {
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);

  function handleNextClick() {
    setIndex(index + 1);
  }

  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}
      />
    </>
  );
}

Hyvä idea on tehdä useita tilamuuttujia jos ne eivät liity toisiinsa, kuten index ja showMore tässä esimerkissä. Mutta jos löydät itsesi usein vaihtamassa kahta tilamuuttujaa yhdessä, saattaa olla helpompaa yhdistää ne yhteen. Esimerkiksi, jos sinulla on lomake monilla kentillä, voi olla kätevää pitää tila yhdessä oliossa ennemin kuin yksi tilamuuttuja per kenttä. Tilarakenteen päättäminen -sivulla on enemmän vinkkejä tähän.

Syväsukellus

Miten React tietää minkä tilan palauttaa?

Olet saattanut huomata, että useState kutsu ei vastaanota tietoa siitä mitä tilamuuttujaa se vastaa. Ei ole mitään “tunnistetta”, joka välitettäisiin useState:lle, joten miten se tietää minkä tilamuuttujan palauttaa? Nojaako se johonkin taikaan kuten funktioiden parsimiseen? Ei.

Sen sijaan, mahdollistaakseen niiden tiiviin syntaksin, hookit turvautuvat vakaaseen kutsujärjestykseen saman komponentin jokaisella renderöinnillä. Tämä toimii hyvin käytännössä, koska jos seuraat ylhäällä mainittua sääntöä (“kutsu hookkeja vain ylätasossa”), hookit tullaan kutsumaan aina samassa järjestyksessä. Lisäksi, lintteri-lisäosa huomaa suurimman osan virheistä.

Sisäisesti, React pitää yllään listan tilamuuttujapareista jokaiselle komponentille. Se pitää yllään myös sen hetkistä indeksiä, joka on asetettu 0:ksi ennen renderöintiä. Joka kerta kun kutsut useState:a, React antaa sinulle seuraavan tilaparin ja kasvattaa indeksiä. Voit lukea lisää tästä mekanismista linkistä: React Hooks: Not Magic, Just Arrays.

Tämä esimerkki ei käytä Reactia, mutta antaa idean siitä miten useState toimii sisäisesti:

let componentHooks = [];
let currentHookIndex = 0;

// How useState works inside React (simplified).
function useState(initialState) {
  let pair = componentHooks[currentHookIndex];
  if (pair) {
    // This is not the first render,
    // so the state pair already exists.
    // Return it and prepare for next Hook call.
    currentHookIndex++;
    return pair;
  }

  // This is the first time we're rendering,
  // so create a state pair and store it.
  pair = [initialState, setState];

  function setState(nextState) {
    // When the user requests a state change,
    // put the new value into the pair.
    pair[0] = nextState;
    updateDOM();
  }

  // Store the pair for future renders
  // and prepare for the next Hook call.
  componentHooks[currentHookIndex] = pair;
  currentHookIndex++;
  return pair;
}

function Gallery() {
  // Each useState() call will get the next pair.
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);

  function handleNextClick() {
    setIndex(index + 1);
  }

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

  let sculpture = sculptureList[index];
  // This example doesn't use React, so
  // return an output object instead of JSX.
  return {
    onNextClick: handleNextClick,
    onMoreClick: handleMoreClick,
    header: `${sculpture.name} by ${sculpture.artist}`,
    counter: `${index + 1} of ${sculptureList.length}`,
    more: `${showMore ? 'Hide' : 'Show'} details`,
    description: showMore ? sculpture.description : null,
    imageSrc: sculpture.url,
    imageAlt: sculpture.alt
  };
}

function updateDOM() {
  // Reset the current Hook index
  // before rendering the component.
  currentHookIndex = 0;
  let output = Gallery();

  // Update the DOM to match the output.
  // This is the part React does for you.
  nextButton.onclick = output.onNextClick;
  header.textContent = output.header;
  moreButton.onclick = output.onMoreClick;
  moreButton.textContent = output.more;
  image.src = output.imageSrc;
  image.alt = output.imageAlt;
  if (output.description !== null) {
    description.textContent = output.description;
    description.style.display = '';
  } else {
    description.style.display = 'none';
  }
}

let nextButton = document.getElementById('nextButton');
let header = document.getElementById('header');
let moreButton = document.getElementById('moreButton');
let description = document.getElementById('description');
let image = document.getElementById('image');
let sculptureList = [{
  name: 'Homenaje a la Neurocirugía',
  artist: 'Marta Colvin Andrade',
  description: 'Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.',
  url: 'https://i.imgur.com/Mx7dA2Y.jpg',
  alt: 'A bronze statue of two crossed hands delicately holding a human brain in their fingertips.'  
}, {
  name: 'Floralis Genérica',
  artist: 'Eduardo Catalano',
  description: 'This enormous (75 ft. or 23m) silver flower is located in Buenos Aires. It is designed to move, closing its petals in the evening or when strong winds blow and opening them in the morning.',
  url: 'https://i.imgur.com/ZF6s192m.jpg',
  alt: 'A gigantic metallic flower sculpture with reflective mirror-like petals and strong stamens.'
}, {
  name: 'Eternal Presence',
  artist: 'John Woodrow Wilson',
  description: 'Wilson was known for his preoccupation with equality, social justice, as well as the essential and spiritual qualities of humankind. This massive (7ft. or 2,13m) bronze represents what he described as "a symbolic Black presence infused with a sense of universal humanity."',
  url: 'https://i.imgur.com/aTtVpES.jpg',
  alt: 'The sculpture depicting a human head seems ever-present and solemn. It radiates calm and serenity.'
}, {
  name: 'Moai',
  artist: 'Unknown Artist',
  description: 'Located on the Easter Island, there are 1,000 moai, or extant monumental statues, created by the early Rapa Nui people, which some believe represented deified ancestors.',
  url: 'https://i.imgur.com/RCwLEoQm.jpg',
  alt: 'Three monumental stone busts with the heads that are disproportionately large with somber faces.'
}, {
  name: 'Blue Nana',
  artist: 'Niki de Saint Phalle',
  description: 'The Nanas are triumphant creatures, symbols of femininity and maternity. Initially, Saint Phalle used fabric and found objects for the Nanas, and later on introduced polyester to achieve a more vibrant effect.',
  url: 'https://i.imgur.com/Sd1AgUOm.jpg',
  alt: 'A large mosaic sculpture of a whimsical dancing female figure in a colorful costume emanating joy.'
}, {
  name: 'Ultimate Form',
  artist: 'Barbara Hepworth',
  description: 'This abstract bronze sculpture is a part of The Family of Man series located at Yorkshire Sculpture Park. Hepworth chose not to create literal representations of the world but developed abstract forms inspired by people and landscapes.',
  url: 'https://i.imgur.com/2heNQDcm.jpg',
  alt: 'A tall sculpture made of three elements stacked on each other reminding of a human figure.'
}, {
  name: 'Cavaliere',
  artist: 'Lamidi Olonade Fakeye',
  description: "Descended from four generations of woodcarvers, Fakeye's work blended traditional and contemporary Yoruba themes.",
  url: 'https://i.imgur.com/wIdGuZwm.png',
  alt: 'An intricate wood sculpture of a warrior with a focused face on a horse adorned with patterns.'
}, {
  name: 'Big Bellies',
  artist: 'Alina Szapocznikow',
  description: "Szapocznikow is known for her sculptures of the fragmented body as a metaphor for the fragility and impermanence of youth and beauty. This sculpture depicts two very realistic large bellies stacked on top of each other, each around five feet (1,5m) tall.",
  url: 'https://i.imgur.com/AlHTAdDm.jpg',
  alt: 'The sculpture reminds a cascade of folds, quite different from bellies in classical sculptures.'
}, {
  name: 'Terracotta Army',
  artist: 'Unknown Artist',
  description: 'The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.',
  url: 'https://i.imgur.com/HMFmH6m.jpg',
  alt: '12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor.'
}, {
  name: 'Lunar Landscape',
  artist: 'Louise Nevelson',
  description: 'Nevelson was known for scavenging objects from New York City debris, which she would later assemble into monumental constructions. In this one, she used disparate parts like a bedpost, juggling pin, and seat fragment, nailing and gluing them into boxes that reflect the influence of Cubism’s geometric abstraction of space and form.',
  url: 'https://i.imgur.com/rN7hY6om.jpg',
  alt: 'A black matte sculpture where the individual elements are initially indistinguishable.'
}, {
  name: 'Aureole',
  artist: 'Ranjani Shettar',
  description: 'Shettar merges the traditional and the modern, the natural and the industrial. Her art focuses on the relationship between man and nature. Her work was described as compelling both abstractly and figuratively, gravity defying, and a "fine synthesis of unlikely materials."',
  url: 'https://i.imgur.com/okTpbHhm.jpg',
  alt: 'A pale wire-like sculpture mounted on concrete wall and descending on the floor. It appears light.'
}, {
  name: 'Hippos',
  artist: 'Taipei Zoo',
  description: 'The Taipei Zoo commissioned a Hippo Square featuring submerged hippos at play.',
  url: 'https://i.imgur.com/6o5Vuyu.jpg',
  alt: 'A group of bronze hippo sculptures emerging from the sett sidewalk as if they were swimming.'
}];

// Make UI match the initial state.
updateDOM();

Sinun ei tarvitse ymmärtää tätä käyttääksesi Reactia, mutta saatat kokea sen hyödyllisenä.

Tila on eristetty ja yksityinen

Tila on paikallinen kussakin komponentissa. Toisin sanoen, jos renderöit saman komponentin kahdesti, kummallakin on niiden oma eristetty tila! Yhden muuttaminen ei vaikuta toiseen.

Tässä esimerkissä, aiempi Gallery komponetti renderöidään kahdesti ilman muutoksia sen logiikkaan. Kokeile klikata painikkeita kummassakin galleriassa. Huomaat, että niiden tilat ovat itsenäisiä:

import Gallery from './Gallery.js';

export default function Page() {
  return (
    <div className="Page">
      <Gallery />
      <Gallery />
    </div>
  );
}

Tämä tekee tilasta erilaisen kuin tavalliset muuttujat, joita saatat määritellä moduulisi yläosassa. Tilaa ei ole yhdistetty tiettyyn funktiokutsuun tai paikkaan koodissa, mutta se on “paikallinen” siinä kohtaa ruutua. Renderöit kaksi <Gallery /> komponenttia, joten niiden tila on tallennettu erillään.

Huomaa myös kuinka Page komponentti ei “tiedä” mitään Gallery:n tilasta tai edes onko sillä tilaa. Toisin kuin propsit, tila on täysin yksityinen komponentille, joka sen määrittelee. Yläkomponetti ei voi muuttaa sitä. Tämän avulla voit lisätä tilan mihin tahansa komponenttiin tai poistaa sen vaikuttamatta muihin komponentteihin.

Entä jos haluaisit molempien gallerioiden pitäbän niiden tilan synkronisoituna? Oikea tapa tehdä tämä Reactissa on poistamalla tila alakomponenteista ja listätä se niiden lähimpään jaettuun yläkomponenttiin. Seuraavat muutamat sivut keskittyvät yhden komponentin tilan järjestämiseen , mutta palaamme tähän aiheeseen Sharing State Between Components -sivulla.

Kertaus

  • Käytä tilamuuttujaa kun komponentin täytyy “muistaa” jotain tietoa renderien välillä.
  • Tilamuuttujat määritellään kutsumalla useState hookkia.
  • Hookit ovat erityisiä funktioita, jotka alkavat sanalla use. Niiden avulla voit “koukata” Reactin toimintoihin kuten tilaan.
  • Hookit saattavat muistuttaa sinua importeista: ne pitää kutsua ilman ehtoja. Hookkien kutsuminen, useState mukaanlukien, on sallittua vain komponentin yläosassa tai toisessa hookissa.
  • useState hookki palauttaa arvoparin: nykyisen tilan ja funktion jolla päivittää sitä.
  • Voit tehdä useita tilamuuttujia. Sisäisesti, React yhdistää ne järjestyksen perusteella.
  • Tila on yksityistä komponentille. Jos renderöit niitä kahdessa paikassa, kukin saa oman tilan.

Kun painat “Next” painiketta viimesellä veistoksella, koodi kaatuu. Korjaa logiikka joka estää kaatumista. Voit tehdä tämän lisäämällä logiikkaa tapahtumakäsittelijään tai poistamalla painikkeen käytöstä kun toimintoa ei ole mahdollista tehdä.

Korjaamisen jälkeen, lisää “Previous” painike, joka näyttää edellisen veistoksen. Sen ei tulisi kaatua ensimmäisessä veistoksessa.

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

export default function Gallery() {
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);

  function handleNextClick() {
    setIndex(index + 1);
  }

  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}
      />
    </>
  );
}