Reagointi syötteen tilaan

React tarjoaa deklaratiivisen tavan käsitellä käyttöliittymää. Sen sijaan, että manipuloisit suoraan käyttöliittymän yksittäisiä osia, määrittelet eri tilat, joissa komponenttisi voi olla, ja vaihdat niiden välillä käyttäjän syötteen perusteella. Tämä muistuttaa sitä, miten suunnittelijat ajattelevat käyttöliittymästä.

Tulet oppimaan

  • Miten deklaratiiviinen käyttöliittymäohjelmointi eroaa imperatiivisesta käyttöliittymäohjelmoinnista
  • Kuinka luetella eri visuaaliset tilat, joissa komponenttisi voi olla?
  • Miten käynnistää muutokset eri visuaalisten tilojen välillä koodista käsin?

Miten deklaratiivinen käyttöliittymä vertautuu imperatiiviseen

Kun suunnittelet käyttöliittymän vuorovaikutusta, luultavasti mietit, miten käyttöliittymä muuttuu käyttäjän toimien seurauksena. Ajattele lomaketta, jonka avulla käyttäjä voi lähettää vastauksen:

  • Kun kirjoitat jotain lomakkeeseen, “Lähetä” painike tulee käyttöön.
  • Kun painat “Lähetä”, sekä lomake että painike poistuu käytöstä, ja latausikoni tulee näkyviin.
  • Jos verkkopyyntö onnistuu, lomake piiloutuu, ja “Kiitos” viesti tulee näkyviin.
  • Jos verkkopyyntö epäonnistuu, virheviesti tulee näkyviin, ja lomake tulee käyttöön uudelleen.

Imperatiivisessa ohjelmoinnissa, edellä mainittu viittaa suoraan siihen miten vuorovaikutus toteutetaan. Sinun täytyy kirjoittaa tarkat ohjeet käyttöliittymän manipulointiin sen perusteella mitä juuri tapahtui. Toinen tapa ajatella tätä on: Kuvittele, että olet autossa jonkun vieressä ja kerrot hänelle mihin käännytään joka käännökseltä.

Autossa, jota ohjaa ahdistuneen näköinen henkilö, joka edustaa JavaScriptiä, matkustaja käskee kuljettajaa suorittamaan mutkikkaan navigointisarjan.

Illustrated by Rachel Lee Nabors

Hän ei tiedä mihin haluat mennä, noudattavat vain käskyjäsi. (Ja jos annat vääriä ohjeita, päädyt väärään paikkaan!) Tätä kutsutaan imperatiiviseksi, koska jokaista elementtiä täytyy “käskeä”, latausikonista painikkeeseen, kertoen tietokoneelle miten päivittää käyttöliittymää.

Tässä imperatiivisen käyttöliittymäohjelmoinnin esimerkissä lomake on rakennettu ilman Reactia. Se käyttää vain selaimen sisäänrakennettua DOM:a.

async function handleFormSubmit(e) {
  e.preventDefault();
  disable(textarea);
  disable(button);
  show(loadingMessage);
  hide(errorMessage);
  try {
    await submitForm(textarea.value);
    show(successMessage);
    hide(form);
  } catch (err) {
    show(errorMessage);
    errorMessage.textContent = err.message;
  } finally {
    hide(loadingMessage);
    enable(textarea);
    enable(button);
  }
}

function handleTextareaChange() {
  if (textarea.value.length === 0) {
    disable(button);
  } else {
    enable(button);
  }
}

function hide(el) {
  el.style.display = 'none';
}

function show(el) {
  el.style.display = '';
}

function enable(el) {
  el.disabled = false;
}

function disable(el) {
  el.disabled = true;
}

function submitForm(answer) {
  // Oletetaan, että se yhdistäisi verkkoon.
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (answer.toLowerCase() == 'istanbul') {
        resolve();
      } else {
        reject(new Error('Good guess but a wrong answer. Try again!'));
      }
    }, 1500);
  });
}

let form = document.getElementById('form');
let textarea = document.getElementById('textarea');
let button = document.getElementById('button');
let loadingMessage = document.getElementById('loading');
let errorMessage = document.getElementById('error');
let successMessage = document.getElementById('success');
form.onsubmit = handleFormSubmit;
textarea.oninput = handleTextareaChange;

Käyttöliittymän manipulointi imperatiivisesti toimii hyvin eristetyissä esimerkeissä, mutta siitä tulee eksponentiaalisesti hankalempaa hallita monimutkaisissa järjestelmissä. Kuvittele, että päivität sivua täynnä erilaisia lomakkeita kuten tämä. Uuden käyttöliittymäelementin tai vuorovaikutuksen lisääminen vaatisi huolellista koodin tarkistusta, ettet ole luonut bugia (esimerkiksi, unohtanut näyttää tai piilottaa jotain).

React on rakennettu ratkaisemaan tämä ongelma.

Reactissa et suoraan manipuloi käyttöliittymää—tarkoittaen, että et ota käyttöön, poista käytöstä, näytä/piilota komponentteja suoraan. Sen sijaan määrittelet mitä haluat näyttää, ja React päättelee miten käyttöliittymä päivitetään. Kuvittele olevasi taksissa ja kerrot kuskille mihin haluat mennä sen sijaan, että kertoisit mihin kääntyä. On kuskin tehtävä viedä sinut sinne ja hän saattaa jopa tietää joitain oikoteita, joita et ole saattanut ottaa huomioon!

Reactin ohjaamassa autossa matkustaja pyytää päästä tiettyyn paikkaan kartalla. React selvittää, miten se tehdään.

Illustrated by Rachel Lee Nabors

Käyttöliittymän ajatteleminen deklaratiivisesti

Nyt olet nähnyt ylhäällä miten lomake toteutetaan imperatiivisesti. Jotta ymmärtäisit paremmin, miten Reactissa ajatellaan, käydään alla läpi tämän käyttöliittymän uudelleen toteuttaminen Reactissa:

  1. Tunnista komponenttisi eri visuaaliset tilat
  2. Määritä mikä käynnistää nämä tilamuutokset
  3. Edusta tila muistissa käyttäen useState hookkia
  4. Poista kaikki epäolennaiset tilamuuttujat
  5. Yhdistä tapahtumakäsittelijät tilan asettamiseksi

1. Vaihe: Tunnista komponenttisi eri visuaaliset tilat

Tietojenkäsittelytieteessä olet saattanut kuulla “tilakoneesta”, joka voi olla yhdessä useista “tiloista”. Jos työskentelet suunnittelijan kanssa, olet saattanut nähdä mallinnuksia erilaisista “visuaalisista tiloista”. React on suunnittelun ja tietotekniikan risteyskohta, joten molemmat ideat ovat inspiraation lähteitä.

Ensiksi, täytyy visualisoida kaikki käyttöliittymän eri “tilat”, joita käyttäjä saattaa nähdä:

  • Tyhjä: Lomakkeen “Lähetä” painike on poissa käytöstä.
  • Kirjoittaa: Lomakkeen “Lähetä” painike on käytössä.
  • Lähettää: Lomake on kokonaan poissa käytöstä. Latausikoni näkyy.
  • Onnistui: “Kiitos” viesti näkyy lomakkeen sijaan.
  • Virhe: Sama kuin Kirjoittaa -tila, mutta lisävirheviestillä.

Juuri kuten suunnittelija haluat “mallintaa” tai luoda “malleja” eri tiloihin ennen kuin lisäät logiikkaa. Esimerkiksi, tässä on malli vain lomakkeen visuaaliselle osalle. Tämä malli ohjataan status propsin kautta, jonka oletusarvona on 'empty':

export default function Form({
  status = 'empty'
}) {
  if (status === 'success') {
    return <h1>That's right!</h1>
  }
  return (
    <>
      <h2>City quiz</h2>
      <p>
        In which city is there a billboard that turns air into drinkable water?
      </p>
      <form>
        <textarea />
        <br />
        <button>
          Submit
        </button>
      </form>
    </>
  )
}

Voisit nimetä propsin miten haluat, nimi ei nyt ole niin tärkeää. Kokeile muokata propsi status = 'empty' arvoon status = 'success' nähdäksesi onnistumisviestin. Mallintamisen avulla voit nopeasti iteroida käyttöliittymää ennen kuin lisäät logiikkaa. Tässä on täyteläisempi prototyyppi samasta komponentista, joka silti ohjataan status propsilla:

export default function Form({
  // Try 'submitting', 'error', 'success':
  status = 'empty'
}) {
  if (status === 'success') {
    return <h1>That's right!</h1>
  }
  return (
    <>
      <h2>City quiz</h2>
      <p>
        In which city is there a billboard that turns air into drinkable water?
      </p>
      <form>
        <textarea disabled={
          status === 'submitting'
        } />
        <br />
        <button disabled={
          status === 'empty' ||
          status === 'submitting'
        }>
          Submit
        </button>
        {status === 'error' &&
          <p className="Error">
            Good guess but a wrong answer. Try again!
          </p>
        }
      </form>
      </>
  );
}

Syväsukellus

Monien visuaalisten tilojen näyttäminen kerralla

Jos komponentilla on monia visuaalisia tiloja, voi olla kätevää näyttää ne kaikki samalla sivulla:

import Form from './Form.js';

let statuses = [
  'empty',
  'typing',
  'submitting',
  'success',
  'error',
];

export default function App() {
  return (
    <>
      {statuses.map(status => (
        <section key={status}>
          <h4>Form ({status}):</h4>
          <Form status={status} />
        </section>
      ))}
    </>
  );
}

Tämänkaltaisia sivuja usein kutsutaan “eläviksi tyyliohjeiksi” tai “storybookeiksi”.

2. Vaihe: Määritä, mikä laukaisee nämä tilamuutokset

Voit käynnistää tilamuutoksen vastauksena kahdenlaiseen syötteeseen:

  • Ihmisen syötteeseen, kuten painikeen klikkaaminen, tekstin kirjoittaminen, linkkiin navigoiminen.
  • Tietokoneen syötteeseen, kuten verkkopyynnön vastauksen saapuminen, aikakatkaisun päättyminen, kuvan lataaminen.
Sormi.
Ihmisen syötteet
Ykkösiä ja nollia.
Tietokoneen syötteet

Illustrated by Rachel Lee Nabors

Molemmissa tapauksissa, saatat asettaa tilamuuttujia käyttöliittymän päivittämiseksi. Kehittämässäsi lomakkeessa sinun on vaihdettava tilaa muutaman eri syötteen perusteella:

  • Tekstinsyötön muuttaminen (ihminen) tulisi muuttaa Tyhjä tila Kirjoitetaan -tilaan tai päin vastoin, riippuen siitä onko syöttökenttä tyhjä vai ei.
  • Lähetä -painikkeen klikkaaminen (ihminen) tulisi muuttaa tila Lähetetään arvoon.
  • Onnistunut verkkovastaus (tietokone) tulisi muuttaa tila Onnistunut arvoon.
  • Epäonnistunut verkkovastaus (tietokone) tulisi muuttaa tila Virhe arvoon itse virheviestin kanssa.

Huomaa

Huomaa, että ihmisen syötteet usein vaativat tapahtumakäsittelijöitä!

Ymmärtääksesi tämän prosessin paremmin voit kokeilla piirtää paperille jokaisen tilan ympyräksi ja jokaisen tilamuutoksen nuolina. Voit hahmotella monia prosesseja tällä tavoin ja selvittää virheet kauan ennen toteutusta.

Virtauskaavio vasemmalta oikealle, jossa on 5 ympyrää. Ensimmäisessä ympyrässä, jossa on merkintä 'empty', on yksi nuoli, jossa on merkintä 'start typping' ja joka on yhteydessä ympyrään, jossa on merkintä 'typping'. Kyseisessä ympyrässä on yksi nuoli, jossa on merkintä 'press submit', joka on yhteydessä ympyrään, jossa on merkintä 'submitting', jossa on kaksi nuolta. Vasemmanpuoleinen nuoli on merkitty 'network error', joka liittyy ympyrään 'error'. Oikea nuoli on merkitty merkinnällä 'network success' ja se on yhteydessä ympyrään, jonka nimi on 'success'.
Virtauskaavio vasemmalta oikealle, jossa on 5 ympyrää. Ensimmäisessä ympyrässä, jossa on merkintä 'empty', on yksi nuoli, jossa on merkintä 'start typping' ja joka on yhteydessä ympyrään, jossa on merkintä 'typping'. Kyseisessä ympyrässä on yksi nuoli, jossa on merkintä 'press submit', joka on yhteydessä ympyrään, jossa on merkintä 'submitting', jossa on kaksi nuolta. Vasemmanpuoleinen nuoli on merkitty 'network error', joka liittyy ympyrään 'error'. Oikea nuoli on merkitty merkinnällä 'network success' ja se on yhteydessä ympyrään, jonka nimi on 'success'.

Lomakkeen tilat

3. Vaihe: Esitä tila muistissa käyttämällä useState:a

Seuraavaksi sinun täytyy esitellä komponenttisi visuaaliset tilat muistissa useState. hookilla. Yksinkertaisuus on avainasemassa: jokainen osa tilaa on “liikkuva osa”, ja haluat niin vähän “liikkuvia osia” kuin mahdollista. Suurempi monimutkaisuus johtaa useampiin virheisiin!

Aloita tilalla, jonka on ehdottomasti oltava siellä. Sinun on esimerkiksi tallennettava answer syötettä varten ja error (jos se on olemassa) viimeisimmän virheen tallentamiseksi:

const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);

Sitten tarvitset tilamuuttujan, joka kuvaa, minkä aiemmin kuvatuista visuaalisista tiloista haluat näyttää. Muistissa on yleensä useampi kuin yksi tapa esittää tämä, joten sinun täytyy kokeilla sitä.

const [isEmpty, setIsEmpty] = useState(true);
const [isTyping, setIsTyping] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const [isError, setIsError] = useState(false);

Ensimmäinen ideasi ei todennäköisesti ole paras mahdollinen, mutta se ei haittaa - tilan muokkaaminen on osa prosessia!

4. Vaihe: Poista kaikki epäolennaiset tilamuuttujat

Haluat välttää toistoa tilasisällössä, jotta seuraat vain olennaisia asioita. Jos käytät hieman aikaa tilarakenteesi uudistamiseen, komponenttisi ovat helpommin ymmärrettävissä, toistoa vähennetään ja tahattomia merkityksiä vältetään. Tavoitteenasi on estää tapaukset, joissa muistissa oleva tila ei edusta mitään pätevää käyttöliittymää, jonka haluaisit käyttäjän näkevän. (Et esimerkiksi koskaan halua näyttää virheilmoitusta ja poistaa syötettä käytöstä samaan aikaan, tai käyttäjä ei pysty korjaamaan virhettä!).

Tässä on joitakin kysymyksiä, joita voit kysyä tilamuuttujiltasi:

  • Aiheuttaako tämä tila paradoksin? Esimerkiksi, isTyping ja isSubmitting eivät voi molemmat olla arvoltaan true. Paradoksi tarkoittaa yleensä sitä, että tilaa ei ole tarpeeksi rajattu. Kahden totuusarvon yhdistelmiä voi olla neljä, mutta vain kolme vastaa kelvollisia tiloja. Jos haluat poistaa “mahdottoman” tilan, voit yhdistää nämä arvot “tilaksi”, jonka on oltava yksi kolmesta arvosta: 'typing', 'submitting', tai 'success'.
  • Ovatko samat tiedot jo saatavilla toisessa tilamuuttujassa? Toinen paradoksi: isEmpty ja isTyping eivät voi olla arvoltaan true samaan aikaan. Tekemällä niistä erilliset tilamuuttujat, vaarana on, että ne menevät sekaisin ja aiheuttavat virheitä. Onneksi voit poistaa isEmpty ja sen sijaan tarkistaa answer.length === 0.
  • Voiko saman tiedon saada toisen tilamuuttujan käänteisluvusta? isError:ia ei tarvita, sillä voit sen sijaan tarkistaa error !== null.

Tämän siivouksen jälkeen jäljelle jää 3 (7:stä!) välttämätöntä tilamuuttujaa:

const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing'); // 'typing', 'submitting', tai 'success'

Tiedät, että ne ovat välttämättömiä, kun et voi poistaa yhtään niistä rikkomatta toiminnallisuutta.

Syväsukellus

Mahdottomien tilojen poistaminen reducerilla

Nämä kolme muuttujaa ovat tarpeeksi kuvaamaan tämän lomakkeen tilaa. Kuitenkin, on jotain välitiloja, jotka eivät ole järkeviä. Esimerkiksi, ei-null error ei ole järkevä kun status on success. Tilan tarkemmaksi mallintamiseksi, voit käyttää reduceria. Reducerien avulla voit yhdistää useita tilamuuttujia yhdeksi olioksi ja tiivistää liittyvät logiikat yhteen!

5. Vaihe: Yhdistä tapahtumakäsittelijät tilan asettamiseen

Lopuksi, luo tapahtumakäsittelijät, jotka asettavat tilamuuttujat. Alla on lopullinen lomake, jossa kaikki tapahtumakäsittelijät on kytketty:

import { useState } from 'react';

export default function Form() {
  const [answer, setAnswer] = useState('');
  const [error, setError] = useState(null);
  const [status, setStatus] = useState('typing');

  if (status === 'success') {
    return <h1>That's right!</h1>
  }

  async function handleSubmit(e) {
    e.preventDefault();
    setStatus('submitting');
    try {
      await submitForm(answer);
      setStatus('success');
    } catch (err) {
      setStatus('typing');
      setError(err);
    }
  }

  function handleTextareaChange(e) {
    setAnswer(e.target.value);
  }

  return (
    <>
      <h2>City quiz</h2>
      <p>
        In which city is there a billboard that turns air into drinkable water?
      </p>
      <form onSubmit={handleSubmit}>
        <textarea
          value={answer}
          onChange={handleTextareaChange}
          disabled={status === 'submitting'}
        />
        <br />
        <button disabled={
          answer.length === 0 ||
          status === 'submitting'
        }>
          Submit
        </button>
        {error !== null &&
          <p className="Error">
            {error.message}
          </p>
        }
      </form>
    </>
  );
}

function submitForm(answer) {
  // Pretend it's hitting the network.
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let shouldError = answer.toLowerCase() !== 'lima'
      if (shouldError) {
        reject(new Error('Good guess but a wrong answer. Try again!'));
      } else {
        resolve();
      }
    }, 1500);
  });
}

Vaikka tämä koodi ei ole enää alkuperäinen imperatiivinen esimerkki, se on kestävempi. Kaikkien vuorovaikutuksien ilmaiseminen tilamuutoksina antaa sinun ottaa käyttöön uusia visuaalisia tiloja rikkomatta olemassa olevia tiloja. Se myös antaa sinun muuttaa mitä tulisi näyttää eri tiloissa muuttamatta toimintalogiikkaa itsessään.

Kertaus

  • Deklaratiivinen ohjelmointi tarkoittaa käyttöliittymän kuvaamista jokaiselle visuaaliselle tilalle toisin kuin käyttöliittymän mikromanagerointi (imperatiivinen).
  • Komponenttia kehitettäessä:
    1. Tunnista kaikki sen visuaaliset tilat.
    2. Määritä ihmisen ja tietokoneen aiheuttamat tilamuutokset.
    3. Mallinna tila useState:lla.
    4. Poista epäolennainen tila välttääksesi bugeja ja paradokseja.
    5. Yhdistä tapahtumakäsittelijät tilan asettamiseen.

Haaste 1 / 3:
Lisää ja poista CSS luokka

Toteuta tämä siten, että kuvan klikkaaminen poistaa background--active CSS luokan sitä ympäröivästä <div>, mutta lisää picture--active luokan <img> elementtiin. Taustan klikkaaminen uudestaan palauttaa alkuperäiset luokat.

Visuaalisesti tulisi odottaa, että klikkaaminen poistaa violetin taustan ja korostaa kuvan reunoja. Kuvan ulkopuolelta klikkaaminen korostaa kuvan taustaa, mutta poistaa kuvan reunojen korostuksen.

export default function Picture() {
  return (
    <div className="background background--active">
      <img
        className="picture"
        alt="Rainbow houses in Kampung Pelangi, Indonesia"
        src="https://i.imgur.com/5qwVYb1.jpeg"
      />
    </div>
  );
}