Tutoriál: Bevezetés a Reactbe
Ez a tutoriál nem feltételez korábbi React ismereteket.
Mielőtt elkezdjük a Tutoriált
Ebben a tutoriálban egy kis játékot fogunk készíteni. Csábító lehet átugrani, mivel nem játékokat készítesz — de azért adj neki egy esélyt. A technikák, amiket itt tanulsz, alapvetőek bármilyen React alkalmazáshoz, és ha ezeket sikerül elsajátítanod, úgy sokkal jobban meg fogod érteni a React működését.
Tipp
Ez a tutoriál azoknak szól, akik a gyakorlatias tanulást szeretik. Ha te az alap koncepciók tanulását preferálod, nézd meg a lépésről-lépésre útmutatónkat. Elképzelhető, hogy ezt a tutoriált és az útmutatót egymás kiegészítésének találod majd.
Ez a tutoriál szekciókra van felosztva:
- Beállítások a Tutoriálhoz ad egy kezdőpontot a tutoriál követéséhez.
- Áttekintés megtanítja a React alapokat: komponensek, propok, és a state (állapot).
- Játék befejezése segít a leggyakoribb technikák elsajátításában a Reactben.
- Időutazás hozzáadása egy átfogóbb képet ad a React egyedi erősségeiről.
Nem kell minden egyes szekciót egyszerre befejezned, hogy eredményes légy. Próbálj meg olyan messzire eljutni, amennyire tudsz — akkor is, ha ez egy, vagy két szekció.
Mit készítünk?
Ebben a tutoriálban megmutatjuk, hogy hogyan készíthetsz egy tic-tac-toe játékot Reactben.
Itt megnézheted, hogy mit is készítünk: Végeredmény. Ha a kódot nem teljesen érted, vagy ha nem világos a szintaxis, ne aggódj! A tutoriál célja, hogy megértesse veled a Reactet, és annak szintaxisát.
Ajánljuk, hogy mielőtt folytatnád ezt a tutoriált, olvass utána a tic-tac-toe játéknak. Az egyik funkció amit észrevehetsz, az a jobb oldalon lévő számozott lista a játék táblájáról. Ez a lista tartalmazza az összes korábbi lépést, ami a játék menete során folyamatosan frissül.
Ha megismerkedtél a tic-tac-toe játékkal, nyugodtan zárd be. Ez a tutoriál egy egyszerű sablont használ kiindulópontként. A következő lépés felkészíteni téged, hogy elkezdhessük a játék fejlesztését.
Előfeltételek
Feltételezzük, hogy van már valami tapasztalatod a HTML-el és JavaScripttel, de a tutoriál követése akkor sem lehet probléma, ha egy másik programozói nyelvből jössz. Továbbá feltételezzük, hogy olyan programozói koncepciók, mint a függvények, objektumok, tömbök és - egy bizonyos fokig - az osztályok is ismertek számodra.
Ha először szeretnéd átnézni a JavaScriptet, akkor ezt az útmutatót ajánljuk. Megjegyzés: Ebben a tutoriálban pár ES6 (a JavaScript jelenlegi verziója) funkciót is használunk, többek között nyílfunkciókat, osztályokat, let
, és const
utasításokat. A Babel REPL segítségével leellenőrízheted, hogy az ES6 mivé lesz lefordítva.
Beállítások a Tutoriálhoz
Ezt a tutoriált kétféleképpen is elvégezheted: kódolhatsz a böngésződből, vagy felállíthatsz egy helyi fejlesztői környezetet.
1. Opció: kódolj a böngésződben
A leggyorsabban így állhatsz neki!
Először is nyisd meg a Kezdő kódot egy új fülön. Az új fül egy új, üres tic-tac-toe táblát és a React kódot kell, hogy mutassa. Ez a React kód az, amit ebben a tutoriálban szerkeszteni fogunk.
Ugord át a második opciót, és a React áttekintéséhez menj az Áttekintés szekcióhoz.
2. Opció: helyi fejlesztői környezet
Ez az opció szabadon választható, és nem kötelező a tutoriál elvégzéséhez!
Választható: Instrukciók helyi környezetből való követéshez, a kedvenc szövegszerkesztődhöz
Ahhoz, hogy követni tudd a tutoriált egy általad választott szerkesztőből, ez az opció kicsivel több beállítást igényel. Íme a lépések:
- Győződj meg róla, hogy a Node.js egy jelenlegi verziója telepítve van.
- Kövesd a Create React App telepítési útmutatóját egy új projekt létrehozásához.
npx create-react-app my-app
- Törölj minden fájlt az új projekt
src/
mappájában.
Megjegyzés:
Ne töröld az egész
src
mappát, csak az eredeti forrásfájlokat a mappában. A következő lépésben ki fogjuk cserélni az alap forrásfájlokat ebben a projektben.
cd my-app
cd src
# Ha Macet, vagy Linuxot használsz:
rm -f *
# Vagy ha Windowson vagy:
del *
# Ezután lépj vissza a projekt mappába
cd ..
- Hozz létre egy
index.css
fájlt asrc/
mappában, tartalma pedig legyen ez a CSS kód. - Hozz létre egy
index.js
fájlt asrc/
mappában, tartalma pedig legyen ez a JS kód. - Add hozzá a következő három sort az
index.js
fájl tetejéhez, asrc/
mappában:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
Ha mindent jól csináltál, és most lefuttatod az npm start
parancsot a projekt mappájában, és megnyitod a http://localhost:3000
-t a böngészőben, egy üres tic-tac-toe mezőt kell, hogy láss.
Szintaxis kiemeléshez a következő instrukciókat ajánljuk.
Segítség, elakadtam!
Ha bármikor elakadsz, a közösségi támogatási források segíthet. Különösen a Reactiflux Chat lehet hasznos, ha gyorsan szeretnél segítséget kapni. Ha nem érkezik válasz, vagy még mindig el vagy akadva, nyiss egy issue-t és segítünk.
Áttekintés
Most, hogy minden készen áll, kezdjük a React áttekintésével!
Mi az a React?
A React egy deklaratív, effektív, és rugalmas JavaScript könyvtár, felhasználói felületek készítéséhez. Lehetővé teszi komplex felhasználói felületek összeállítását izolált kódrészletekből, amiket “komponenseknek” hívunk.
A React rendelkezik egy pár komponenstípussal, de most kezdjük a React.Component
alosztállyal:
class ShoppingList extends React.Component {
render() {
return (
<div className="shopping-list">
<h1>{this.props.name} bevásárlólistája</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
<li>Oculus</li>
</ul>
</div>
);
}
}
// Példa használata: <ShoppingList name="Mark" />
Nemsokára beszélünk a vicces XML-szerű címkékről is. A komponensek segítségével mondjuk meg a Reactnek, hogy mit szeretnénk látni a képernyőn. Ha az adatunk megváltozik, a React hatékonyan frissíti és újrarendereli a komponensünket.
Itt a ShoppingList egy React komponensosztály, vagy React komponenstípus. Egy komponens paramétereket fogad, amiket props-nak hívunk (angol “properties” rövidítése), és egy nézethierarchiát ad vissza, a render
metóduson keresztül.
A render
metódus egy leírását adja vissza annak, amit a képernyőn szeretnél látni. A React fogja a leírást, és megjeleníti az eredményt. Pontosabban, a render
metódus egy React elem-et ad vissza, ami egy könnyűsúlyú leírása annak, amit renderelni kell. A legtöbb React fejlesztő egy speciális szintaxist használ, ezt “JSX”-nek hívják, ami könnyebbé teszi ezen struktúrák írását. A <div />
szintaxist React.createEelement('div')
-té transzformáljuk kompiláláskor. A fenti példa egyenértékű az alábbival:
return React.createElement('div', {className: 'shopping-list'},
React.createElement('h1', /* ... h1 gyermekei ... */),
React.createElement('ul', /* ... ul gyermekei ... */)
);
Ha érdekel a createElement()
részletesebb leírása, nézd meg az API referenciát, de ebben a tutoriálban ezt nem fogjuk használni. A JSX-et viszont igen.
A JSX rendelkezik a JavaScript minden erejével. A JSX-be bármilyen JavaScript kifejezést tehetsz, kapcsos zárójelek közé. Minden React elem egy JavaScript objektum, amit változókban tárolhatsz, vagy körbeküldhetsz a programodban.
A fenti ShoppingList
komponens csak beépített DOM komponenseket renderel, mint a <div />
és az <li />
. De összeállíthatsz és renderelhetsz egyedi React komponenseket is. Például a <ShoppingList />
írásával utalhatunk az egész bevásárlólistára. Minden React komponens elzártan és függetlenül operálhat; ez lehetővé teszi számodra komplex felhasználói kezelőfelületek építését egyszerű komponensekből.
Kezdő kód ellenőrzése
Ha a tutoriálon a böngésződből fogsz dolgozni, nyisd meg ezt a kódot egy új fülön: Kezdő kód. Ha helyi környezetben fogsz dolgozni, nyisd meg a src/index.js
fájlt a projekt mappádban (már korábban szerkesztetted a fájlt a beállítások részben).
Ez a Kezdő kód az alapja annak, amit készítünk. A CSS stíluslapot megadtuk, hogy csak a Reactre és a tic-tac-toe játék programozására kelljen fókuszálnod.
A kód tanulmányozásával megállapíthatod, hogy háromféle React komponensünk van:
- Square
- Board
- Game
A Square komponens egy egyszerű <button>
-t renderel, amíg a Board 9 Square-t. A Game komponens egy játéktáblát renderel helyőrző értékekkel, amiket később módosítunk. Jelenleg nincs egyetlen interaktív komponens sem.
Adattovábbítás propokkal
Hogy végre bemocskoljuk a kezünk, küldjünk adatot a Board komponensből a Square komponensnek.
Erősen ajánljuk, hogy minden kódot kézzel írj a tutoriál során, és ne használj másolás/beillesztést. Ez hozzá fog járulni ahhoz, hogy jobban megértsd mi is történik, és később minden magától jön majd.
A Board renderSquare
metódusában változtasd meg a kódot, hogy egy value
propot tudj küldeni a Square komponensnek:
class Board extends React.Component {
renderSquare(i) {
return <Square value={i} />; }
}
Változtasd meg a Square komponens render
metódusát úgy, hogy átírod a {/* TODO */}
részt {this.props.value}
-ra:
class Square extends React.Component {
render() {
return (
<button className="square">
{this.props.value} </button>
);
}
}
Előtte:
Utána: Egy számot kell láss minden négyzetben a renderelés során.
Nézd meg a teljes kódot ezen a ponton
Gratulálunk! Sikeresen “leküldtél egy propot” egy szülő Board komponensből egy gyermek Square komponensnek. React alkalmazásokban a propok leküldésével tudsz információt mozgatni szülőktől gyermek komponenseknek.
Készíts egy interaktív komponenst
Töltsük ki a Square komponenst egy “X”-szel, rákattintás esetén.
Először is változtasd meg a button címkét a render()
metódus visszatérésében erre:
class Square extends React.Component {
render() {
return (
<button className="square" onClick={function() { alert('kattintás'); }}> {this.props.value}
</button>
);
}
}
Ha most kattintasz a Square-re, egy értesítést kell láss a böngésződben.
Megjegyzés
A kevesebb gépelés és a
this
félreérthető viselkedésének elkerülése érdekében, innentől a nyílfunkció szintaxist fogjuk használni az eseménykezelőkhöz:class Square extends React.Component { render() { return ( <button className="square" onClick={() => alert('kattintás')}> {this.props.value} </button> ); } }
Vedd észre, hogy az
onClick={() => alert('kattintás')}
segítségével egy függvényt küldünk le propkéntonClick
néven. A React csak kattintás után fogja meghívni ezt a függvényt. Gyakori hiba csak ennyit írnionClick={alert('kattintás')}
, és elfelejteni a() =>
részt. Ez meghívná a függvényt a komponens minden újrarenderelésénél.
Következő lépésként azt próbáljuk elérni, hogy a Square komponens “emlékezzen” arra, hogy rá lett kattintva, és töltse ki magát egy “X”-szel. Ahhoz, hogy komponensek “emlékezni” tudjanak, state-t (állapotot) használnak.
React komponensekben állapotot a this.state
segítségével deklarálhatunk a konstruktorban. A this.state
állapotra úgy kell tekintenünk, hogy az privát legyen abban az osztályban, amiben az definiálva lett. Tároljuk a Square jelenlegi értékét a this.state
objektumban, és változtassuk azt meg, ha a Square-re kattintunk.
Először is adjunk hozzá egy konstruktort az osztályhoz, hogy inicializáljuk az állapotot:
class Square extends React.Component {
constructor(props) { super(props); this.state = { value: null, }; }
render() {
return (
<button className="square" onClick={() => alert('kattintás')}>
{this.props.value}
</button>
);
}
}
Megjegyzés
JavaScript osztályokban mindig meg kell hívnod a
super
metódust, amikor definiálod a konstruktort egy alosztályban. Minden React komponensosztály, ami rendelkezik egyconstructor
-ral, egysuper(props)
hívással kell, hogy kezdődjön.
Most pedig változtassuk meg a Square render
metódusát, hogy az állapot jelenlegi értékét mutassa:
- Cseréld ki a
this.props.value
-tthis.state.value
-ra, a<button>
címkében. - Cseréld ki az
onClick={...}
eseménykezelőt erre:onClick={() => this.setState({value: 'X'})}
. - Tedd a
className
ésonClick
propokat külön sorokba, a jobb olvashatóság érdekében.
Ezen változtatások után a <button>
címke, amit a Square render
metódusa visszaad, így néz ki:
class Square extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null,
};
}
render() {
return (
<button
className="square" onClick={() => this.setState({value: 'X'})} >
{this.state.value} </button>
);
}
}
Amikor a Square komponens render
metódusában az onClick
kezelő meghívja a this.setState
metódust, a React minden alkalommal újra fogja renderelni a Square komponenst, amikor a <button>
elemre rákattintunk. A frissítés után a Square this.state.value
értéke 'X'
lesz, tehát egy X
-szet fogunk látni a játéktáblán. Ha bármelyik Square-re kattintasz, egy X
kell hogy megjelenjen.
Amikor a setState
metódust meghívjuk egy komponensben, a React automatikusan frissíti annak minden gyermek komponensét is.
Nézd meg a teljes kódot ezen a ponton
Fejlesztői eszközök
A React Devtools fejlesztői eszközök kiegészítő Chrome-hoz és Firefox-hoz segít a React komponens fa vizsgálatában, a böngésződ fejlesztői eszközeivel.
A React DevTools segít ellenőrizni a React komponenseid propjait és állapotait (state).
A React DevTools telepítése után kattints bármelyik elemre jobb egérgombbal az oldalon, majd kattints a “Vizsgálat”-ra a fejlesztői eszközök megnyitásához. Ekkor a React fülek (“⚛️ Components” és “⚛️ Profiler”) megjelennek a jobb oldalon utolsó két fülként. A komponensfa vizsgálatához használd a “⚛️ Components” fület.
Ha azonban a CodePen-en dolgozol, szükség lesz pár extra lépésre ahhoz, hogy egy működjön:
- Jelentkezz be, vagy regisztrálj, és erősítsd meg az e-mailed (spam elkerülése érdekében).
- Kattints a “Fork” gombra.
- Kattints a “Change View”-ra, majd válaszd a “Debug mode”-t.
- Az új megnyíló fülön, a fejlesztői eszközökben lesz egy React fül.
Játék befejezése
Kész vagyunk a tic-tac-toe játék alap építőelemeivel. Egy teljes játékhoz azonban az “X”-szek és “O”-k elhelyezésének a váltakozására van szükségünk a játéktáblán, és szükségünk van egy módra, hogy megállapíthassuk a győztest.
Állapot felemelése
Jelenleg minden Square komponens külön kezeli a játék állapotát. A győztes ellenőrzéséhez mind a 9 négyzet értékét egy helyen fogjuk kezelni.
Azt gondolhatnánk, hogy a Board komponens csak egyszerűen végigkérdezi minden Square állapotát. Bár ez lehetséges a Reactben, nem támogatjuk, mert így a kód nehezen érthetővé válik, fogékony lesz hibákra, és nehéz lesz újraírni. Ehelyett a legjobb módszer az, ha a játék állapotát a szülő Board komponensben tároljuk minden Square komponens helyett. A Board komponens meg tudja mondani minden Square komponensnek mit mutasson propok leküldésével, ahogyan egy számot is leküldtünk minden Square komponensnek
Több gyermekből való adatgyűjtéshez, vagy ahhoz, hogy két gyermekkomponens kommunikálni tudjon egymással, az állapotot, amin a komponensek osztozni fognak, egy közös szülőkomponensben kell, hogy deklaráld. A szülőkomponens ezután le tudja küldeni a megosztott állapotot a gyermekeknek propok segítségével; ez szinkronizálja a gyermekeket egymással és a szülő komponensükkel.
A helyi állapot felemelése egy szülő komponensbe gyakori tevékenység, amikor React komponenseket írunk újra. — Ragadjuk meg az alkalmat, és próbáljuk ezt ki gyakorlatban.
Adj hozzá egy konstruktort a Board komponenshez, és állítsd be a kezdő állapotot, hogy az tartalmazzon egy tömböt 9 null értékkel, amik a 9 négyzetnek felelnek meg:
class Board extends React.Component {
constructor(props) { super(props); this.state = { squares: Array(9).fill(null), }; }
renderSquare(i) {
return <Square value={i} />;
}
Amikor később a táblát töltjük ki, a this.state.sqares
tömb valahogy így fog kinézni:
[
'O', null, 'X',
'X', 'X', 'O',
'O', null, null,
]
A Board komponens renderSquare
metódusa jelenleg így néz ki:
renderSquare(i) {
return <Square value={i} />;
}
Kezdetben, ahhoz számokat mutassunk 0-tól 8-ig minden Square komponensben, leküldtük a value
propot a Board komponensből. Egy másik korábbi lépésben lecseréltük a számokat “X” jelekre, amit a Square komponens saját állapota határozott meg. A Square komponens jelenleg ezért nem veszi figyelembe a value
prop értékét, amit a Board komponens küld neki.
Most megint igénybe fogjuk venni a propküldési mechanizmust. Úgy módosítjuk a Board komponenst, hogy az értesítsen minden Square komponenst annak jelenlegi értékéről ('X'
, 'O'
, vagy null
). A squares
tömböt már korábban definiáltuk a Board komponens konstruktorában, és most úgy módosítjuk a Board komponens renderSquare
metódusát, hogy az ebből a tömbből olvasson:
renderSquare(i) {
return <Square value={this.state.squares[i]} />; }
Nézd meg a teljes kódot ezen a ponton
Így most minden Square komponens fogadni fog egy value
propot, ami lehet 'X'
, 'O'
, vagy üres négyzetek esetében null
.
A következőben meg kell változtatnunk mi történjen, ha a Square komponensre rákattintanak. Most már a Board komponens kezeli melyik négyzetek vannak kitöltve. Valahogy el kell érnünk, hogy a Square komponens frissítse a Board állapotát. Mivel az állapot privát arra a komponensre nézve amiben az definiálva van, a Board állapota nem frissíthető közvetlenül a Square komponensből.
Ehelyett leküldünk egy függvényt a Board komponensből a Square-nek, és hagyjuk, hogy a Square meghívja ezt a függvényt, amikor egy négyzetre kattintanak. Ehhez meg kell változtatnunk a renderSquare
metódust a Board komponensben:
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)} />
);
}
Megjegyzés
Az olvashatóság érdekében a visszaküldött elemet több sorba tördeljük, és hozzáadunk zárójeleket is, hogy a JavaScript ne adjon pontosvesszőt a
return
után, ezzel elrontva a kódunkat.
Így már két propot küldünk le a Board-ból a Square-nek: value
és onClick
. Az onClick
prop egy függvény, amit a Square meg tud hívni, ha rákattintanak. A Square komponensen a következő változásokat eszközöljük:
- Cseréld le a
this.state.value
-tthis.props.value
-ra a Squarerender
metódusában - Cseréld le a
this.setState()
-tthis.props.onClick()
-re a Squarerender
metódusában - Töröld a
constructor
-t a Square komponensből, mivel az többé nem követi a játék állását
A Square komponens így néz ki a változtatások után:
class Square extends React.Component { render() { return (
<button
className="square"
onClick={() => this.props.onClick()} >
{this.props.value} </button>
);
}
}
Amikor a Square komponensre rákattintanak, az onClick
függvény meg lesz hívva, amit a Board komponens szolgáltat. Íme egy összefoglaló, hogy ez hogyan is lehetséges:
- Az
onClick
prop a beépített DOM<button>
komponensben közli a Reacttel, hogy állítson fel egy eseményfigyelőt kattintásra. - Amikor a gombra kattintanak, a React meghívja az
onClick
eseményfigyelőt, ami a Square komponensrender()
metódusában definiálva lett. - Ez az eseményfigyelő meghívja a
this.props.onClick()
függvényt. A SquareonClick
propja a Board komponensben lett definiálva. - Mivel a Board leküldte az
onClick={() => this.handleClick(i)}
propot a Square komponensnek, a Square meghívja athis.handleClick(i)
függvényt, ha rákattintanak. - Mivel a
handleClick()
metódust még nem definiáltuk, a kódunk összeomlik. Ha most kattintasz egy négyzetre, egy piros hibát kell látnod a képernyőn, ami valami olyat mond, hogy “this.handleClick is not a function”, azaz “a this.handleClick nem függvény”.
Megjegyzés
A
<button>
gombelemonClick
attribútuma speciális jelentéssel bír a React számára, mivel ez egy beépített komponens. Egy egyedi komponens esetén, mint például a Square, a megnevezés tőled függ. A SquareonClick
propjának más nevet is adhatunk, vagy akár a BoardhandleClick
metódusának, és a kód ugyanúgy működne. A Reactben, közös megállapodás alapjánon[Event]
-nek hívjuk azokat a propokat, amik eseményeket képviselnek éshandle[Event]
-nek azokat a metódusokat amik eseményeket kezelnek.
Ha rákattintunk egy Square komponensre, egy hibát kell hogy kapjunk, mivel a handleClick
még nincs definiálva. Most hozzáadjuk a handleClick
metódust a Board osztályhoz:
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
};
}
handleClick(i) { const squares = this.state.squares.slice(); squares[i] = 'X'; this.setState({squares: squares}); }
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
);
}
render() {
const status = 'Következő játékos X';
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
Nézd meg a teljes kódot ezen a ponton
Ezen változtatások után újra rá tudunk kattintani a Square komponensekre, hogy kitöltsük őket, ahogy eddig is. Azonban az állapot most már a Board komponensben van tárolva ahelyett, hogy minden Square külön tárolná a saját állapotát. Ha a Board állapota megváltozik, a Square komponens automatikusan újra fog renderelni. Minden négyzet állapotának a Board komponensben való tárolása lehetővé teszi, hogy a jövőben megállapítsuk a győztest.
Mivel a Square komponens többé nem kezel állapotot, a Square komponens most már csak a Board komponenstől fogad értékeket, és értesíti azt, ha rákattintanak. React nyelven ez azt jelenti, hogy a Square komponensek kontrollált komponensek. A Board komponens teljes mértékben irányítja őket.
Vedd észre, hogy a handleClick
metódusban meghívjuk a .slice()
metódust a tömbön, hogy a squares
tömb egy másolatát módosítsuk, ne az eredetit. A következő szekcióban elmagyarázzuk, hogy miért készítjük ezt a squares
tömb másolatot.
Megváltoztathatatlanság fontossága
Az előző kódpéldában azt tanácsoltuk, hogy a .slice()
metódussal készítsünk egy squares
tömb másolatot, hogy ne az eredeti tömböt módosítsuk. Most megvitatjuk a megváltoztathatatlanságot, és hogy miért fontos ennek megtanulása.
Adatváltoztatásra két általános megközelítés létezik. Az első megközelítés az, hogy közvetlenül megváltoztatjuk az adat értékét. A második megközelítés lecserélni az adatot egy másolattal, ami tartalmazza a kívánt változtatásokat.
Adatváltozás mutációval
var player = {score: 1, name: 'György'};
player.score = 2;
// A player most {score: 2, name: 'György'}
Adatváltozás mutáció nélkül
var player = {score: 1, name: 'György'};
var newPlayer = Object.assign({}, player, {score: 2});
// A player változatlan, a newPlayer pedig {score: 2, name: 'György'}
// Vagy ha használod az objektum kiterítési szintaxisjavaslatot, ezt is írhatod:
// var newPlayer = {...player, score: 2};
A végeredmény ugyanaz, de azzal, hogy nem közvetlenül változtatjuk meg az eredeti (vagy eredeti alá rendelt) adatot, hasznos lehet a lent leírt okokból.
Komplex tulajdonságok válnak egyszerűvé
A megváltoztathatatlansággal bonyolult tulajdonságok válnak egyszerűen implementálhatóvá. Ebben a tutoriálban később implementálni fogunk egy “időutazó” funkciót, ami lehetővé teszi számunkra a tic-tac-toe játék lépéstörténetének tanulmányozását, és abban való korábbi lépésre való “visszaugrást”. Ez a funkció nem egyedi játékokra — a visszavonási képesség, vagy újracsinálni bizonyos tevékenységeket, egy gyakori követelmény alkalmazásokban. A közvetlen adatváltozás elkerülésével érintetlenül tudjuk tartani a játék lépéstörténetének korábbi verzióit, és ezt később újrahasznosíthatjuk.
Változások észlelése
A változások észlelése megváltoztatható objektumokban nehéz feladat, mivel azok közvetlenül módosíthatóak. Ez az észlelés megköveteli a megváltoztatható objektumtól, hogy azt összehasonlítsuk saját maga korábbi másolataihoz és a teljes objektumfa bejárását.
A változások észlelése egy megváltoztathatatlan objektum esetén jelentősen egyszerűbb. Ha a megváltoztathatatlan objektum, amire referálunk különbözik a korábbitól, akkor az objektum megváltozott.
Újrarenderelés megállapítása Reactben
A megváltoztathatatlanság legfőbb előnye, hogy az segít a Reactben tiszta komponensek építésében. A megváltoztathatatlan adat könnyen megállapíthatja, ha valamilyen változás történt, ami azt segít meghatározni, hogy egy komponensnek újra kell-e renderelnie.
A shouldComponentUpdate()
metódusról, és hogy hogyan készíts tiszta komponenseket, a Teljesítmény optimalizálása olvasásával tanulhatsz többet.
Függvénykomponensek
Most átalakítjuk a Square komponenst egy függvénykomponenssé.
A Reactben a függvénykomponensek egy egyszerűbb módja komponensek írásának, amik csak egy render
metódust tartalmaznak, és nincs saját állapotuk. Ahelyett hogy osztályokat definiálunk, amik a React.Component
kiterjesztései, írhatunk egy függvényt, ami veszi a prop
-okat inputként, és azt adja vissza, amit renderelni kéne. A függvénykomponensek írása kevésbé időigényes, és sok komponens kifejezhető így.
Cseréld le a Square osztályt erre a függvényre:
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
Lecseréltük a this.props
-t props
-ra mindkét helyen, ahol megjelent.
Nézd meg a teljes kódot ezen a ponton
Megjegyzés
Amikor módosítottuk a Square függvényt egy függvénykomponensre, az
onClick={() => this.props.onClick()}
-t is megváltoztattuk egy rövidebbonClick={props.onClick}
formára (vedd észre mindkét zárójel hiányát).
Szerepváltás
Most pedig ki kell javítanunk egy egyértelmű hibát a tic-tac-toe játékunkban: “O”-kat még nem lehet felvinni a táblára.
Most beállítjuk az “X”-szet alapértelmezett első lépésnek. Az alapértelmezett lépést a kezdő állapot módosításával állíthatjuk be, a Board konstruktorában:
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
xIsNext: true, };
}
Minden alkalommal, mikor egy játékos lép, az xIsNext
(egy boolean) értékét váltogatjuk, hogy meg tudjuk határozni melyik játékos következik, és hogy a játék állása el legyen mentve. Módosítsuk a Board handleClick
metódusát, hogy váltogassa az xIsNext
értékét:
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = this.state.xIsNext ? 'X' : 'O'; this.setState({
squares: squares,
xIsNext: !this.state.xIsNext, });
}
Ezzel a változtatással, az “X”-szek és az “O”-k most már váltakoznak. Próbáld ki!
Változtassuk meg a “státusz” szöveget is a Board render
metódusában, hogy azt is mutassa, hogy ki a következő játékos:
render() {
const status = 'Következő játékos: ' + (this.state.xIsNext ? 'X' : 'O');
return (
// más változás nincs
Ezen változtatások után a Board komponensed így kell hogy kinézzen:
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
xIsNext: true, };
}
handleClick(i) {
const squares = this.state.squares.slice(); squares[i] = this.state.xIsNext ? 'X' : 'O'; this.setState({ squares: squares, xIsNext: !this.state.xIsNext, }); }
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
);
}
render() {
const status = 'Következő játékos ' + (this.state.xIsNext ? 'X' : 'O');
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
Nézd meg a teljes kódot ezen a ponton
Győztes kihirdetése
Most hogy már mutatjuk ki a következő játékos, azt is mutatnunk kéne, ha a játékot valaki megnyeri, és nincs több lépésre szükség. Másold be ezt a segédfüggvényt a fájl végére:
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
Adott egy 9 négyzetet tartalmazó tömb, ez a függvény leellenőrzi, hogy van-e győztes, és ennek megfelelően visszaadhat 'X'
-szet, 'O'
-t, vagy null
értéket.
A calculateWinner(squares)
függvényt meghívjuk a Board render
metódusában, hogy megtudjuk van-e győztes. Ha egy játékos nyert, akkor mutathatunk valami olyan szöveget mint: “Győztes: X” vagy “Győztes: O”. Cseréljük le a status
deklarációját is a Board render
metódusában erre a kódra:
render() {
const winner = calculateWinner(this.state.squares); let status; if (winner) { status = 'Győztes: ' + winner; } else { status = 'Következő játékos ' + (this.state.xIsNext ? 'X' : 'O'); }
return (
// más változás nincs
Most már megváltoztathatjuk a Board handleClick
metódusát is, hogy az előbb térjen vissza, ignorálva a kattintásokat, ha valaki megnyerte a játékot, vagy ha a Square már ki van töltve:
handleClick(i) {
const squares = this.state.squares.slice();
if (calculateWinner(squares) || squares[i]) { return; } squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
});
}
Nézd meg a teljes kódot ezen a ponton
Gratulálunk! Most már van egy működő tic-tac-toe játékod. És közben megtanultad a React alapjait is. Szóval igazából itt te vagy az igazi nyertes.
Időutazás hozzáadása
Egy utolsó gyakorlatként tegyük lehetővé az “időutazást” korábbi játékállásokhoz.
Lépéstörténet tárolása
Ha mutáltuk (közvetlen megváltoztatás) volna a squares
tömböt, az időutazás implementálása nagyon bonyolult lenne.
Mi azonban a slice()
metódust használtuk új squares
tömb másolatok létrehozásához minden lépésnél, és így megváltoztathatatlanként kezeltük azt. Ez lehetővé teszi majd a squares
tömb minden korábbi verziójának eltárolását, és hogy navigálni tudjunk a már megtörtént lépések között.
A korábbi squares
tömböket egy új, history
nevű tömbben fogjuk tárolni. A history
tömb a tábla minden állását tartalmazza az elsőtől az utolsó lépésig, és így néz ki:
history = [
// Az első lépés előtt
{
squares: [
null, null, null,
null, null, null,
null, null, null,
]
},
// Az első lépés után
{
squares: [
null, null, null,
null, 'X', null,
null, null, null,
]
},
// A második lépés után
{
squares: [
null, null, null,
null, 'X', null,
null, null, 'O',
]
},
// ...
]
Most el kell döntenünk, hogy melyik komponens birtokolja a history
állapotot.
Állapot ismételt felemelése
Szeretnénk, ha a legfelsőbb szintű Game komponens mutatná a korábbi lépések listáját. Ehhez hozzá kell hogy férjen a history
-hoz, szóval a history
állapotot a legfelsőbb szintű Game komponensbe helyezzük.
A history
állapot Game komponensben való elhelyezésével eltávolíthatjuk a squares
állapotot annak gyermekkomponenséből, a Board komponensből. Ugyanúgy, ahogy a Square komponensből “felemeltük az állapotot” a Board komponensbe, most felemeljük azt a Board-ból a legfelsőbb szintű Game komponensbe. Ez teljes felügyeletet ad a Game komponensnek a Board adatai felett, és utasíthatja a Board komponenst korábbi állások renderelésére a history
-ból.
Először is beállítjuk a kezdeti állapotot a Game komponens konstruktorában:
class Game extends React.Component {
constructor(props) { super(props); this.state = { history: [{ squares: Array(9).fill(null), }], xIsNext: true, }; }
render() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<div>{/* státusz */}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
);
}
}
Ezután beállítjuk, hogy a Board komponens a Game komponensből fogadja a squares
és onClick
propokat. Mivel már van egy egyszerű kattintáskezelőnk a Board-ban több Square komponenshez, meg kell mondanunk minden egyes Square komponens pozícióját is az onClick
kezelőnek, hogy jelezzük melyik Square-re kattintottak. Íme a szükséges lépések a Board komponens átalakításához:
- Töröld a
constructor
-t a Board-ban. - Cseréld le a
this.state.squares[i]
-tthis.props.squares[i]
-re a BoardrenderSquare
metódusában. - Cseréld le a
this.handleClick(i)
-tthis.props.onClick(i)
-re a BoardrenderSquare
metódusában.
A Board komponens most így néz ki:
class Board extends React.Component {
handleClick(i) {
const squares = this.state.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
});
}
renderSquare(i) {
return (
<Square
value={this.props.squares[i]} onClick={() => this.props.onClick(i)} />
);
}
render() {
const winner = calculateWinner(this.state.squares);
let status;
if (winner) {
status = 'Győztes: ' + winner;
} else {
status = 'Következő játékos ' + (this.state.xIsNext ? 'X' : 'O');
}
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
Frissítsük a Game komponens render
metódusát, hogy az a legfrissebb history bejegyzést használja a játék státuszának megjelenítéséhez:
render() {
const history = this.state.history; const current = history[history.length - 1]; const winner = calculateWinner(current.squares); let status; if (winner) { status = 'Győztes: ' + winner; } else { status = 'Következő játékos ' + (this.state.xIsNext ? 'X' : 'O'); }
return (
<div className="game">
<div className="game-board">
<Board squares={current.squares} onClick={(i) => this.handleClick(i)} /> </div>
<div className="game-info">
<div>{status}</div> <ol>{/* TODO */}</ol>
</div>
</div>
);
}
Mivel most már a Game komponens rendereli a játék státuszát, eltávolíthatjuk az ennek megfelelő kódot a Board render
metódusából. Az újraírás után a Board render
metódusa így néz ki:
render() { return ( <div> <div className="board-row"> {this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
Utoljára pedig át kell helyeznünk a handleClick
metódust a Board komponensből a Game komponensbe. Módosítanunk is kell a handleClick
-et, mivel a Game komponens állapota másként van strukturálva.
A Game handleClick
metódusában összefűzzük a history bejegyzéseket a history
állapotba.
handleClick(i) {
const history = this.state.history; const current = history[history.length - 1]; const squares = current.squares.slice(); if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
history: history.concat([{ squares: squares, }]), xIsNext: !this.state.xIsNext,
});
}
Megjegyzés
A tömb
push()
metódusával ellentétben, amit már korábbról ismerhetsz, aconcat()
metódus nem módosítja a korábbi tömböt, szóval ezt részesítjük előnyben.
Ezen a ponton a Board komponensnek csak a renderSquare
és render
metódusokra van szüksége. A játék állapota és a handleClick
metódusok a Game komponensben kell legyenek.
Nézd meg a teljes kódot ezen a ponton
Korábbi lépések mutatása
Mivel feljegyezzük a tic-tac-toe játék lépéstörténetét, most már mutatni tudjuk a játékosnak a korábbi lépések listáját.
Korábban megtanultuk, hogy a React elemek elsőosztályú JavaScript objektumok; körbe tudjuk őket küldözgetni az alkalmazásban. Hogy több elemet tudjunk renderelni Reactben, használhatunk egy tömböt, ami React elemeket tartalmaz.
A JavaScriptben a tömbök rendelkeznek egy map()
metódussal, amit gyakran használnak adatok más adatra leképezésére, mint például:
const numbers = [1, 2, 3];
const doubled = numbers.map(x => x * 2); // [2, 4, 6]
A map
metódus használatával le tudjuk képezni a lépéstörténetünket React elemekre, amik gombokat képviselnek a képernyőn, és egy gomblistára, amikkel “ugrálni” tudunk korábbi lépésekre.
Most pedig képezzük le
a history
-t a Game komponens render
metódusában:
render() {
const history = this.state.history;
const current = history[history.length - 1];
const winner = calculateWinner(current.squares);
const moves = history.map((step, move) => { const desc = move ? 'Menj ide, lépés: #' + move : 'Menj a játék kezdetéhez'; return ( <li> <button onClick={() => this.jumpTo(move)}>{desc}</button> </li> ); });
let status;
if (winner) {
status = 'Győztes: ' + winner;
} else {
status = 'Következő játékos ' + (this.state.xIsNext ? 'X' : 'O');
}
return (
<div className="game">
<div className="game-board">
<Board
squares={current.squares}
onClick={(i) => this.handleClick(i)}
/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol> </div>
</div>
);
}
Nézd meg a teljes kódot ezen a ponton
A tic-tac-toe játék lépéstörténetében minden lépéshez létrehozunk egy <li>
-t, ami tartalmaz egy <button>
gombot. A gomb rendelkezik egy onClick
kezelővel, ami meghív egy this.jumpTo()
metódust. A jumpTo()
metódust még nem implementáltuk. Egyenlőre egy listát kell látnunk a lépésekről, amik már megtörténtek a játék során, és egy figyelmeztetést a fejlesztői eszközözök konzolban, ami azt mondja:
Warning: Each child in an array or iterator should have a unique “key” prop. Check the render method of “Game”.
Beszéljük meg, mit is jelent a fenti figyelmeztetés.
Azonosító kulcs választása
Amikor egy listát renderelünk, a React minden renderelt listaelemről tárol valamennyi információt. Amikor frissítünk egy listát, a Reactnek tudnia kell, hogy mi változott. Hozzá tudtunk adni, eltávolítani vagy megváltoztatni a lista sorrendjét, vagy megváltoztatni a lista elemeit.
Képzeld el az átmenetet erről
<li>Aladár: 7 feladat van vissza</li>
<li>Balázs: 5 feladat van vissza</li>
erre
<li>Balázs: 9 feladat van vissza</li>
<li>Klaudia: 8 feladat van vissza</li>
<li>Aladár: 5 feladat van vissza</li>
Ha valaki ezt olvasná a frissített számlálók mellett, valószínűleg azt mondaná, hogy Aladár és Balázs fel lett cserélve, és Klaudia be lett illesztve Aladár és Balázs közé. Azonban a React egy számítógépes program, nem érti mit is szerettünk volna elérni. Mivel a React nem ismeri a szándékainkat, minden listabejegyzésnek meg kell adnunk egy kulcs tulajdonságot, hogy az meg tudja különböztetni a bejegyzéseket annak testvéreitől. Egy alternatíva lehet a sztringek aladar
, balazs
, klaudia
használata. Ha az adatokat egy adatbázisból jelenítenénk meg, akkor használhatnánk Aladár, Balázs és Klaudia adatbázis azonosítókulcsát egyedi kulcsnak.
<li key={user.id}>{user.name}: {user.taskCount} feladat van vissza</li>
Mikor egy listát renderelünk újra, a React veszi minden lista elemének a kulcsát, és megkeresi az előző listaelemet a megegyező kulccsal. Ha a jelenlegi lista tartalmaz egy kulcsot, amit korábban nem, a React készít egy új komponenst. Ha a jelenlegi listában már nincs jelen egy korábban létező kulcs, a React törli az előző komponenst. Ha két kulcs egyezik, a komponens mozgatva lett. A kulcsok azonosítanak minden komponenst a Reactnek, ami lehetővé teszi annak az állapotok kezelését újrarenderelések között. Ha egy komponens kulcsa megváltozik, a komponens törölve lesz és újra létre lesz hozva, egy új állapottal.
A key
egy speciális fenntartott tulajdonság a Reactben (a ref
-el egyetemben, ami egy sokkal haladóbb funkció). Mikor egy új elemet készítünk, a React kivonja a key
tulajdonságot, és közvetlenül eltárolja azt a visszaadott elemen. Ha úgy is tűnik, hogy a key
a prop
-ok közé tartozik, a key
-re nem tudunk referálni a this.prop.key
-vel. A React automatikusan használja a key
-t, hogy eldöntse melyik komponenseket frissítse. Egy komponens nem tud annak key
tulajdonságáról érdeklődni.
Erősen ajánlott, hogy dinamikus listák esetében rendes kulcsokat rendelj hozzá a listaelemekhez. Ha nem rendelkezel egy megfelelő kulccsal, fontold meg az adatod átalakítását, hogy létezzen megfelelő kulcs.
Ha nincs kulcs megadva, a React egy figyelmeztetést fog adni, és a tömb indexét fogja megadni alapértelmezett kulcsként. A tömb indexének kulcsként való használata problémás lehet, ha egy lista újra lesz rendezve, vagy új elem lesz hozzáadva, vagy egy elem lesz törölve. A key={i}
határozott megadása elnémítja a figyelmeztetést, de ugyanaz a probléma áll majd fenn, mint a tömbindexek esetében, és a legtöbb esetben nem ajánlott.
A kulcsoknak nem kell globálisan egyedinek lenniük; csupán komponensek, és azok testvérei között kell, hogy egyediek legyenek.
Időutazás implementálása
A tic-tac-toe játék lépéstörténetében minden korábbi lépésnek van egy egyedi azonosítója: ez a lépés sorszáma. A lépések soha nincsenek átrendezve, törölve, beillesztve más lépések közé, szóval ebben az esetben tömbindex használata kulcsként biztonságosnak számít.
A Game komponens render metódusában hozzá tudjuk adni a kulcsot így <li key={move}>
és a React figyelmeztetése a kulcsokról el kell, hogy tűnjön:
const moves = history.map((step, move) => {
const desc = move ?
'Menj erre ide, lépés: #' + move :
'Menj a játék kezdetéhez';
return (
<li key={move}> <button onClick={() => this.jumpTo(move)}>{desc}</button>
</li>
);
});
Nézd meg a teljes kódot ezen a ponton
Bármelyik listabejegyzés gombjára kattintva egy hiba ugrik fel, mert a jumpTo
metódus nincs definiálva. Mielőtt implementálnánk a jumpTo
metódust, adjuk hozzá a stepNumber
-t a Game komponens állapotához, hogy jelezzük melyik lépést látjuk éppen.
Először is add hozzá a stepNumber: 0
-t a kezdeti állapothoz a Game contructor
-ban:
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
history: [{
squares: Array(9).fill(null),
}],
stepNumber: 0, xIsNext: true,
};
}
Ezután definiáljuk a jumpTo
metódust a Game komponensben, hogy frissíteni tudjuk a stepNumber
-t. Valamint az xIsNext
értékét is igazra állítjuk, ha egy páros számot változtatunk:
handleClick(i) {
// ez a metódus nem változott
}
jumpTo(step) { this.setState({ stepNumber: step, xIsNext: (step % 2) === 0, }); }
render() {
// ez a metódus nem változott
}
Most pedig egy pár változtatást eszközölünk a Game handleClick
metódusában, egy négyzetre kattintáskor lesz meghívva.
A hozzáadott stepNumber
állapot most már tükrözi a felhasználó által látott lépést. Ha lépünk egyet, frissítenünk kell a stepNumber
-t azzal, hogy hozzáadjuk azt a this.setState
argumentumához: stepNumber: history.length
. Ez azt biztosítja, hogy ne ragadjunk le mindig ugyanannak a lépésnek a mutatásával minden új lépés után.
Cseréljük le a this.state.history
olvasását is erre: this.state.history.slice(0, this.state.stepNumber + 1)
. Ez azt biztosítja, hogy ha “visszautazunk az időben”, és egy újat lépünk, akkor eltekintünk a “jövő” lépéseitől, amik így már helytelenek lennének.
handleClick(i) {
const history = this.state.history.slice(0, this.state.stepNumber + 1); const current = history[history.length - 1];
const squares = current.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
history: history.concat([{
squares: squares
}]),
stepNumber: history.length, xIsNext: !this.state.xIsNext,
});
}
Végezetül módosítsuk a Game komponens render
metódusát, hogy a mindig legutolsó lépés renderelése helyett, inkább a kiválasztott lépést renderelje, amit stepNumber
határoz meg:
render() {
const history = this.state.history;
const current = history[this.state.stepNumber]; const winner = calculateWinner(current.squares);
// más változás nincs
Ha most rákattintunk bármelyik lépésre a játéktörténetben, a tic-tac-toa tábla azonnal úgy frissül, hogy azt az állást mutassa, ami a rákattintott lépés után történt.
Nézd meg a teljes kódot ezen a ponton
Összegzés
Gratulálunk! Készítettél egy tic-tac-toe játékot ami:
- Lehetővé teszi a tic-tac-toe játszását,
- Kihirdeti a győztest,
- Elmenti a játék lépéstörténetét a játék közben,
- Lehetővé teszi a játékosoknak újranézni a játéktábla korábbi állásait.
Szép munka! Reméljük, hogy ezek után te is úgy érzed, sikerült megragadnod a React működésének lényegét.
Nézd meg a végeredményt itt: Végeredmény.
Ha van egy kis extra időd, vagy szeretnéd gyakorolni a React új képességeidet, íme pár ötlet a tic-tac-toe játék tökéletesítéséhez, nehézség szerint növekvő sorrendben:
- Mutasd minden lépés pozícióját az (oszlop, sor) formátumban a lépéstörténet listában.
- Tedd félkövérré az aktuálisan kiválasztott elemet a lépés listában.
- Írd át a Board komponenst úgy, hogy az két ciklust használjon négyzetek készítéséhez belekódolás helyett.
- Adj hozzá egy kapcsoló gombot, ami lehetővé teszi a lépések szortírozását növekvő vagy csökkenő sorrendben.
- Ha valaki nyer, emeld ki a három négyzetet, ami lehetővé tette a játékosnak a nyerést.
- Ha senki sem nyer, mutass egy üzenetet döntetlen eredményről.
Ezen a tutoriálon keresztül olyan React koncepciókat érintettünk mint elemek, komponensek, propok és állapot. Ezen témák részletesebb magyarázatához nézd meg a dokumentáció többi részét. Ha többet szeretnél tanulni komponensek definiálásról, nézd meg a React.Component
API hivatkozását.