Dodawanie interaktywności
Niektóre elementy na ekranie aktualizują się w odpowiedzi na interakcje użytkownika. Na przykład kliknięcie w galerię obrazów zmienia aktywny obraz. W Reakcie dane, które zmieniają się w czasie, nazywane są stanem (ang. state). Możesz dodać stan do każdego komponentu i aktualizować go w razie potrzeby. W tym rozdziale nauczysz się, jak pisać komponenty obsługujące interakcje, aktualizujące swój stan i wyświetlające różne wyniki w czasie.
W tym rozdziale
- Jak obsługiwać zdarzenia inicjowane przez użytkownika
- Jak sprawić, aby komponenty “pamiętały” informacje za pomocą stanu
- Jak React aktualizuje interfejs użytkownika w dwóch fazach
- Dlaczego stan nie aktualizuje się od razu po jego zmianie
- Jak kolejkować wiele aktualizacji stanu
- Jak zaktualizować obiekt w stanie
- Jak zaktualizować tablicę w stanie
Reagowanie na zdarzenia
React pozwala na dodawanie procedur obsługi zdarzeń (ang. event handlers) do twojej składni JSX. Procedury obsługi zdarzeń to twoje własne funkcje, które zostaną wywołane w odpowiedzi na interakcje użytkownika, takie jak kliknięcia, najechanie kursorem, skupienie na elementach formularza i inne.
Wbudowane komponenty, takie jak <button>
, obsługują jedynie wbudowane zdarzenia przeglądarki, takie jak onClick
. Jednakże możesz też tworzyć własne komponenty i nadawać ich właściwościom obsługującym zdarzenia dowolne nazwy specyficzne dla twojej aplikacji.
export default function App() { return ( <Toolbar onPlayMovie={() => alert('Odtwarzanie!')} onUploadImage={() => alert('Wgrywanie!')} /> ); } function Toolbar({ onPlayMovie, onUploadImage }) { return ( <div> <Button onClick={onPlayMovie}> Odtwórz film </Button> <Button onClick={onUploadImage}> Wgraj obraz </Button> </div> ); } function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); }
Chcesz zgłębić ten temat?
Przeczytaj rozdział Reagowanie na zdarzenia, aby dowiedzieć się, jak dodawać procedury obsługi zdarzeń.
Czytaj dalejStan - pamięć komponentu
Komponenty często muszą zmieniać to, co jest wyświetlane na ekranie w wyniku interakcji. Wpisywanie w formularzu powinno aktualizować jego pole, kliknięcie “następny” w kolejce obrazów powinno zmieniać wyświetlany obraz, kliknięcie “kup” powinno umieścić produkt w koszyku. Komponenty muszą “pamiętać” różne rzeczy: bieżącą wartość pola, bieżący obraz, zawartość koszyka. W Reakcie tego rodzaju pamięć specyficzna dla komponentu nazywana jest stanem.
Możesz dodać stan do komponentu za pomocą hooka useState
. Hooki to specjalne funkcje, które pozwalają twoim komponentom korzystać z funkcjonalności Reacta (stan jest jedną z tych funkcjonalności). Hook useState
pozwala zadeklarować zmienną stanu. Przyjmuje on stan początkowy i zwraca parę wartości: bieżący stan oraz funkcję ustawiającą stan, która pozwala na jego aktualizację.
const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);
Oto jak galeria obrazów wykorzystuje i aktualizuje stan po kliknięciu:
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}> Następny </button> <h2> <i>{sculpture.name} </i> autorstwa {sculpture.artist} </h2> <h3> ({index + 1} of {sculptureList.length}) </h3> <button onClick={handleMoreClick}> {showMore ? 'Ukryj' : 'Pokaż'} detale </button> {showMore && <p>{sculpture.description}</p>} <img src={sculpture.url} alt={sculpture.alt} /> </> ); }
Chcesz zgłębić ten temat?
Przeczytaj rozdział Stan - Pamięć komponentu, aby dowiedzieć się, jak zapamiętywać wartość i aktualizować ją podczas interakcji.
Czytaj dalejRenderowanie i aktualizowanie
Zanim twoje komponenty zostaną wyświetlone na ekranie, muszą zostać wyrenderowane przez Reacta. Zrozumienie kroków w tym procesie pomoże ci zrozumieć, jak wykonuje się twój kod i wyjaśnić jego działanie.
Wyobraź sobie, że twoje komponenty to kucharze w kuchni, którzy przygotowują smaczne dania z dostępnych składników. W tej sytuacji React jest kelnerem, który przyjmuje zamówienia od klientów i przynosi im zamówione potrawy. Ten proces zgłaszania i obsługi interfejsu użytkownika składa się z trzech kroków:
- Wywołanie (ang. triggering) renderowania (przekazanie zamówienia od gościa do kuchni)
- Renderowanie (ang. rendering) komponentu (przygotowanie zamówienia w kuchni)
- Aktualizowanie (ang. committing) drzewa DOM (umieszczenie zamówienia na stole)
Autor ilustracji Rachel Lee Nabors
Chcesz zgłębić ten temat?
Przeczytaj rozdział Renderowanie i aktualizowanie, aby dowiedzieć się o cyklu życia aktualizacji interfejsu użytkownika.
Czytaj dalejStan jako migawka
W przeciwieństwie do zwykłych zmiennych javascriptowych, stan w Reakcie zachowuje się bardziej jak migawka. Ustawienie stanu nie zmienia już istniejącej zmiennej stanu, lecz wywołuje przerenderowanie. Może to być na początki zaskakujące!
console.log(count); // 0
setCount(count + 1); // Żądanie ponownego renderowania z wartością 1
console.log(count); // Nadal 0!
To zachowanie pomaga unikać subtelnych błędów. Oto mała aplikacja do czatu. Spróbuj zgadnąć, co się stanie, jeśli najpierw naciśniesz “Wyślij”, a potem zmienisz odbiorcę na Boba. Czyje imię pojawi się w alert
pięć sekund później?
import { useState } from 'react'; export default function Form() { const [to, setTo] = useState('Alice'); const [message, setMessage] = useState('Cześć'); function handleSubmit(e) { e.preventDefault(); setTimeout(() => { alert(`Wysłano wiadomość "${message}" do użytkownika ${to}`); }, 5000); } return ( <form onSubmit={handleSubmit}> <label> To:{' '} <select value={to} onChange={e => setTo(e.target.value)}> <option value="Alice">Alice</option> <option value="Bob">Bob</option> </select> </label> <textarea placeholder="Message" value={message} onChange={e => setMessage(e.target.value)} /> <button type="submit">Wyślij</button> </form> ); }
Chcesz zgłębić ten temat?
Przeczytaj rozdział Stan jako migawka, aby dowiedzieć się, dlaczego stan wydaje się być “ustalony” i niezmienny wewnątrz funkcji obsługujących zdarzenia.
Czytaj dalejKolejkowanie serii aktualizacji stanu
Ten komponent zawiera błąd: kliknięcie “+3” zwiększa wynik tylko raz.
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>Wynik: {score}</h1> </> ) }
Stan jako migawka wyjaśnia, dlaczego tak się dzieje. Ustawienie stanu tworzy żądanie nowego przerenderowania, ale nie zmienia tego stanu w już działającym kodzie. Dlatego score
nadal wynosi 0
tuż po wywołaniu 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
Możesz naprawić to, przekazując funkcję aktualizującą (ang. updater function) podczas ustawiania stanu. Zauważ, jak zastąpienie setScore(score + 1)
przez setScore(s => s + 1)
naprawia przycisk “+3”. Dzięki temu możesz kolejkować wiele aktualizacji stanu.
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>Wynik: {score}</h1> </> ) }
Chcesz zgłębić ten temat?
Przeczytaj rozdział Kolejkowanie serii aktualizacji stanu, aby dowiedzieć się, jak kolejkować sekwencję aktualizacji stanu.
Czytaj dalejAktualizowanie obiektów w stanie
Stan może przechowywać dowolne wartości javascriptowe, w tym obiekty. Nie powinno się jednak bezpośrednio zmieniać obiektów i tablic, które przechowuje się w stanie Reacta. Zamiast tego, gdy chcesz zaktualizować obiekt lub tablicę, musisz stworzyć nowy obiekt (lub skopiować istniejący), a następnie zaktualizować stan, aby używał tej kopii.
Zazwyczaj używa się składni rozproszenia (ang. spread syntax) ...
, aby skopiować obiekty i tablice, które chcesz zmienić. Na przykład, aktualizacja zagnieżdżonego obiektu może wyglądać tak:
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> Imię: <input value={person.name} onChange={handleNameChange} /> </label> <label> Tytuł: <input value={person.artwork.title} onChange={handleTitleChange} /> </label> <label> Miasto: <input value={person.artwork.city} onChange={handleCityChange} /> </label> <label> Obraz: <input value={person.artwork.image} onChange={handleImageChange} /> </label> <p> <i>{person.artwork.title}</i> {' autorstawa '} {person.name} <br /> (położone w mieście {person.artwork.city}) </p> <img src={person.artwork.image} alt={person.artwork.title} /> </> ); }
Jeśli kopiowanie obiektów w kodzie staje się uciążliwe, możesz użyć biblioteki takiej jak Immer, aby zmniejszyć ilość powtarzającego się kodu:
{ "dependencies": { "immer": "1.7.3", "react": "latest", "react-dom": "latest", "react-scripts": "latest", "use-immer": "0.5.1" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" }, "devDependencies": {} }
Chcesz zgłębić ten temat?
Przeczytaj rozdział Aktualizowanie obiektów w stanie, aby dowiedzieć się, jak poprawnie aktualizować obiekty.
Czytaj dalejAktualizowanie tablic w stanie
Tablice są kolejnym rodzajem zmiennych obiektów javascriptowych, które można przechowywać w stanie i należy je traktować jako tylko do odczytu. Podobnie jak w przypadku obiektów, gdy chcesz zaktualizować tablicę przechowywaną w stanie, musisz stworzyć nową tablicę (lub skopiować istniejącą), a następnie ustawić stan, aby używał nowej tablicy:
import { useState } from 'react'; const initialList = [ { id: 0, title: 'Wielkie brzuchy', seen: false }, { id: 1, title: 'Księżycowy krajobraz', seen: false }, { id: 2, title: 'Terakotowa armia', 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>Lista dzieł sztuki</h1> <h2>Moja lista dzieł sztuki do zobaczenia:</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> ); }
Jeśli kopiowanie tablic w kodzie staje się uciążliwe, możesz użyć biblioteki takiej jak Immer, aby zmniejszyć ilość powtarzającego się kodu:
{ "dependencies": { "immer": "1.7.3", "react": "latest", "react-dom": "latest", "react-scripts": "latest", "use-immer": "0.5.1" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" }, "devDependencies": {} }
Chcesz zgłębić ten temat?
Przeczytaj rozdział Aktualizowanie tablic w stanie, aby dowiedzieć się, jak poprawnie aktualizować tablice.
Czytaj dalejCo dalej?
Przejdź do rozdziału Reagowanie na zdarzenia, aby zacząć zgłębiać ten temat strona po stronie!
Ewentualnie, jeśli już znasz te tematy, dlaczego nie przeczytać rozdziału Zarządzanie stanem?