Tutorial: Tic-Tac-Toe
இந்த tutorial-இல் நீங்கள் ஒரு சிறிய tic-tac-toe game-ஐ உருவாக்கப் போகிறீர்கள். ஏற்கனவே React தெரிந்திருக்க வேண்டும் என்று இந்த tutorial கருதவில்லை. இந்த tutorial-இல் நீங்கள் கற்றுக்கொள்ளும் techniques எந்த React app-ஐயும் உருவாக்க அடிப்படையானவை; இதை முழுமையாகப் புரிந்துகொள்வது React பற்றிய ஆழமான புரிதலை உங்களுக்கு தரும்.
இந்த tutorial பல பிரிவுகளாகப் பிரிக்கப்பட்டுள்ளது:
- tutorial-க்கான setup இந்த tutorial-ஐப் பின்பற்ற ஒரு தொடக்கப் புள்ளி தரும்.
- Overview React-ன் அடிப்படைகளை கற்றுத்தரும்: components, props, மற்றும் state.
- game-ஐ முடித்தல் React development-இல் மிகவும் பொதுவான techniques-ஐ கற்றுத்தரும்.
- time travel சேர்த்தல் React-ன் தனித்துவமான பலங்களைப் பற்றிய ஆழமான insight தரும்.
நீங்கள் என்ன உருவாக்கப் போகிறீர்கள்?
இந்த tutorial-இல், React உடன் interactive tic-tac-toe game ஒன்றை உருவாக்குவீர்கள்.
முடித்த பிறகு அது எப்படி இருக்கும் என்பதை இங்கே பார்க்கலாம்:
import { useState } from 'react'; function Square({ value, onSquareClick }) { return ( <button className="square" onClick={onSquareClick}> {value} </button> ); } function Board({ xIsNext, squares, onPlay }) { function handleClick(i) { if (calculateWinner(squares) || squares[i]) { return; } const nextSquares = squares.slice(); if (xIsNext) { nextSquares[i] = 'X'; } else { nextSquares[i] = 'O'; } onPlay(nextSquares); } const winner = calculateWinner(squares); let status; if (winner) { status = 'வெற்றி பெற்றவர்: ' + winner; } else { status = 'அடுத்த வீரர்: ' + (xIsNext ? 'X' : 'O'); } return ( <> <div className="status">{status}</div> <div className="board-row"> <Square value={squares[0]} onSquareClick={() => handleClick(0)} /> <Square value={squares[1]} onSquareClick={() => handleClick(1)} /> <Square value={squares[2]} onSquareClick={() => handleClick(2)} /> </div> <div className="board-row"> <Square value={squares[3]} onSquareClick={() => handleClick(3)} /> <Square value={squares[4]} onSquareClick={() => handleClick(4)} /> <Square value={squares[5]} onSquareClick={() => handleClick(5)} /> </div> <div className="board-row"> <Square value={squares[6]} onSquareClick={() => handleClick(6)} /> <Square value={squares[7]} onSquareClick={() => handleClick(7)} /> <Square value={squares[8]} onSquareClick={() => handleClick(8)} /> </div> </> ); } export default function Game() { const [history, setHistory] = useState([Array(9).fill(null)]); const [currentMove, setCurrentMove] = useState(0); const xIsNext = currentMove % 2 === 0; const currentSquares = history[currentMove]; function handlePlay(nextSquares) { const nextHistory = [...history.slice(0, currentMove + 1), nextSquares]; setHistory(nextHistory); setCurrentMove(nextHistory.length - 1); } function jumpTo(nextMove) { setCurrentMove(nextMove); } const moves = history.map((squares, move) => { let description; if (move > 0) { description = 'நகர்வு #' + move + '-க்கு செல்'; } else { description = 'game தொடக்கத்துக்கு செல்'; } return ( <li key={move}> <button onClick={() => jumpTo(move)}>{description}</button> </li> ); }); return ( <div className="game"> <div className="game-board"> <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} /> </div> <div className="game-info"> <ol>{moves}</ol> </div> </div> ); } 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; }
இந்த code இப்போது உங்களுக்கு புரியவில்லை என்றாலோ, code syntax உங்களுக்கு பழக்கமில்லையெனிலோ கவலைப்பட வேண்டாம்! React மற்றும் அதன் syntax-ஐப் புரிந்துகொள்ள உதவுவதே இந்த tutorial-ன் நோக்கம்.
tutorial-ஐத் தொடரும் முன் மேலுள்ள tic-tac-toe game-ஐப் பார்த்து முயற்சி செய்ய பரிந்துரைக்கிறோம். நீங்கள் கவனிக்கும் அம்சங்களில் ஒன்று, game board-ன் வலப்புறத்தில் numbered list இருப்பது. இந்த list game-இல் நடந்த எல்லா நகர்வுகளின் history-யையும் தருகிறது; game முன்னேறும்போது அது update ஆகிறது.
முடிக்கப்பட்ட tic-tac-toe game-ஐ சிறிது விளையாடிப் பார்த்த பிறகு தொடர்ந்து scroll செய்யுங்கள். இந்த tutorial-இல் நேரடியான template-இலிருந்து தொடங்குவீர்கள். அடுத்த படி, game-ஐ உருவாக்கத் தொடங்க உங்களை set up செய்வது.
tutorial-க்கான setup
கீழே உள்ள live code editor-இல், வலது மேல் மூலையில் உள்ள Fork-ஐ click செய்து CodeSandbox website மூலம் editor-ஐ புதிய tab-இல் திறக்கவும். CodeSandbox உங்கள் browser-இல் code எழுதவும், நீங்கள் உருவாக்கிய app-ஐ பயனர்கள் எப்படி பார்ப்பார்கள் என்பதை preview செய்யவும் அனுமதிக்கிறது. புதிய tab-இல் காலியான square மற்றும் இந்த tutorial-க்கான starter code காட்டப்பட வேண்டும்.
export default function Square() { return <button className="square">X</button>; }
Overview
இப்போது setup முடிந்ததால், React-ன் overview-ஐப் பார்ப்போம்!
starter code-ஐ inspect செய்தல்
CodeSandbox-இல் மூன்று முக்கிய பிரிவுகளைப் பார்ப்பீர்கள்:
srcfolder-இல்App.js,index.js,styles.cssபோன்ற file-களின் பட்டியல் மற்றும்publicஎன்ற folder உள்ள Files பிரிவு- நீங்கள் தேர்ந்தெடுத்த file-ன் source code-ஐப் பார்ப்பதற்கான code editor
- நீங்கள் எழுதிய code எப்படி காட்டப்படும் என்பதைப் பார்ப்பதற்கான browser பிரிவு
Files பிரிவில் App.js file தேர்ந்தெடுக்கப்பட்டிருக்க வேண்டும். code editor-இல் அந்த file-ன் உள்ளடக்கம் இதுபோல் இருக்க வேண்டும்:
export default function Square() {
return <button className="square">X</button>;
}browser பிரிவு X கொண்ட square ஒன்றை இவ்வாறு காட்ட வேண்டும்:
இப்போது starter code-இல் உள்ள file-களைப் பார்ப்போம்.
App.js
App.js-இல் உள்ள code ஒரு component-ஐ உருவாக்குகிறது. React-இல் component என்பது user interface-ன் ஒரு பகுதியை பிரதிநிதித்துவப்படுத்தும் reusable code துண்டு. உங்கள் application-இல் UI element-களை render, manage, update செய்ய component-கள் பயன்படுத்தப்படுகின்றன. என்ன நடக்கிறது என்பதைப் பார்க்க component-ஐ வரி வரியாகப் பார்ப்போம்:
export default function Square() {
return <button className="square">X</button>;
}முதல் வரி Square என்ற function-ஐ define செய்கிறது. export JavaScript keyword இந்த function-ஐ இந்த file-க்கு வெளியே அணுகக்கூடியதாக ஆக்குகிறது. default keyword, உங்கள் code-ஐப் பயன்படுத்தும் பிற file-களுக்கு இது உங்கள் file-ன் main function என்று சொல்கிறது.
export default function Square() {
return <button className="square">X</button>;
}இரண்டாவது வரி button ஒன்றை return செய்கிறது. return JavaScript keyword-க்கு பிறகு வரும் எதுவும் function-ஐ call செய்தவருக்கு value ஆக return செய்யப்படும் என்பதைக் குறிக்கிறது. <button> ஒரு JSX element. JSX element என்பது நீங்கள் காட்ட விரும்புவதை விவரிக்கும் JavaScript code மற்றும் HTML tag-களின் இணைப்பு. className="square" என்பது button-ஐ எப்படி style செய்ய வேண்டும் என்பதை CSS-க்கு சொல்லும் button property அல்லது prop. X என்பது button-க்குள் காட்டப்படும் text; </button> JSX element-ஐ மூடி, அதன் பின்வரும் content button-க்குள் வைக்கப்படக்கூடாது என்பதை குறிக்கிறது.
styles.css
CodeSandbox-ன் Files பிரிவில் styles.css என குறிக்கப்பட்ட file-ஐ click செய்யுங்கள். இந்த file உங்கள் React app-க்கான style-களை define செய்கிறது. முதல் இரண்டு CSS selector-கள் (* மற்றும் body) உங்கள் app-ன் பெரிய பகுதிகளின் style-ஐ define செய்கின்றன; .square selector, className property square ஆக அமைக்கப்பட்டுள்ள எந்த component-ன் style-ஐ define செய்கிறது. உங்கள் code-இல், அது App.js file-இல் உள்ள Square component-ன் button-க்கு match ஆகும்.
index.js
CodeSandbox-ன் Files பிரிவில் index.js என குறிக்கப்பட்ட file-ஐ click செய்யுங்கள். tutorial-இன் போது இந்த file-ஐ நீங்கள் edit செய்யமாட்டீர்கள்; ஆனால் இது App.js file-இல் நீங்கள் உருவாக்கிய component மற்றும் web browser இடையிலான பாலமாகும்.
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './styles.css';
import App from './App';1-5 வரிகள் தேவையான எல்லா பகுதிகளையும் ஒன்றாக கொண்டு வருகின்றன:
- React
- web browser-களுடன் பேச React-ன் library (React DOM)
- உங்கள் component-களுக்கான style-கள்
App.js-இல் நீங்கள் உருவாக்கிய component
file-ன் மீதமுள்ள பகுதி எல்லா பகுதிகளையும் ஒன்றிணைத்து, final product-ஐ public folder-இல் உள்ள index.html-க்குள் inject செய்கிறது.
board-ஐ உருவாக்குதல்
App.js-க்கு திரும்புவோம். tutorial-ன் மீதமுள்ள பகுதியை பெரும்பாலும் இங்குதான் செய்வீர்கள்.
தற்போது board ஒரே ஒரு square மட்டுமே; ஆனால் உங்களுக்கு ஒன்பது தேவை! இரண்டு square-களை உருவாக்க square-ஐ copy-paste செய்து இப்படிச் செய்ய முயற்சித்தால்:
export default function Square() {
return <button className="square">X</button><button className="square">X</button>;
}இந்த error கிடைக்கும்:
<>...</>?React component-கள் இரண்டு button-கள் போன்ற பல adjacent JSX element-களை அல்ல, ஒரே ஒரு JSX element-ஐ return செய்ய வேண்டும். இதைச் சரிசெய்ய, பல adjacent JSX element-களை wrap செய்ய Fragment-களை (<> மற்றும் </>) இவ்வாறு பயன்படுத்தலாம்:
export default function Square() {
return (
<>
<button className="square">X</button>
<button className="square">X</button>
</>
);
}இப்போது நீங்கள் இதைப் பார்க்க வேண்டும்:
அருமை! இப்போது ஒன்பது square-களைச் சேர்க்க சில முறை copy-paste செய்தால் போதும், பின்னர்…
ஓஹோ! square-கள் எல்லாம் ஒரே வரியில் உள்ளன; நமக்குத் தேவையான board போல grid-இல் இல்லை. இதைச் சரிசெய்ய, உங்கள் square-களை div-கள் மூலம் row-களாக group செய்து சில CSS class-களைச் சேர்க்க வேண்டும். இதையே செய்யும்போது, ஒவ்வொரு square எங்கே காட்டப்படுகிறது என்பதை உறுதிசெய்ய ஒவ்வொன்றுக்கும் எண் கொடுப்பீர்கள்.
App.js file-இல், Square component-ஐ இதுபோல் update செய்யுங்கள்:
export default function Square() {
return (
<>
<div className="board-row">
<button className="square">1</button>
<button className="square">2</button>
<button className="square">3</button>
</div>
<div className="board-row">
<button className="square">4</button>
<button className="square">5</button>
<button className="square">6</button>
</div>
<div className="board-row">
<button className="square">7</button>
<button className="square">8</button>
<button className="square">9</button>
</div>
</>
);
}styles.css-இல் define செய்யப்பட்ட CSS, board-row என்ற className கொண்ட div-களை style செய்கிறது. styled div-களுடன் component-களை row-களாக group செய்துவிட்டதால், உங்கள் tic-tac-toe board தயாராகிறது:
ஆனால் இப்போது ஒரு பிரச்சினை உள்ளது. Square என்று பெயரிடப்பட்ட உங்கள் component இப்போது உண்மையில் square அல்ல. பெயரை Board என மாற்றி அதைச் சரிசெய்வோம்:
export default function Board() {
//...
}இந்த கட்டத்தில் உங்கள் code இதுபோல் இருக்க வேண்டும்:
export default function Board() { return ( <> <div className="board-row"> <button className="square">1</button> <button className="square">2</button> <button className="square">3</button> </div> <div className="board-row"> <button className="square">4</button> <button className="square">5</button> <button className="square">6</button> </div> <div className="board-row"> <button className="square">7</button> <button className="square">8</button> <button className="square">9</button> </div> </> ); }
props மூலம் data-வை pass செய்தல்
அடுத்து, பயனர் square-ஐ click செய்தால் அதன் value-ஐ காலியாக இருப்பதிலிருந்து “X” ஆக மாற்ற விரும்புவீர்கள். இதுவரை board-ஐ உருவாக்கிய விதத்தில், square-ஐ update செய்யும் code-ஐ ஒன்பது முறை (ஒவ்வொரு square-க்கும் ஒருமுறை) copy-paste செய்ய வேண்டியிருக்கும்! copy-paste செய்வதற்கு பதிலாக, React-ன் component architecture messy, duplicated code-ஐத் தவிர்க்க reusable component உருவாக்க அனுமதிக்கிறது.
முதலில், உங்கள் Board component-இலிருந்து முதல் square-ஐ define செய்யும் வரியை (<button className="square">1</button>) புதிய Square component-க்குள் copy செய்யப் போகிறீர்கள்:
function Square() {
return <button className="square">1</button>;
}
export default function Board() {
// ...
}பிறகு JSX syntax-ஐப் பயன்படுத்தி அந்த Square component-ஐ render செய்ய Board component-ஐ update செய்வீர்கள்:
// ...
export default function Board() {
return (
<>
<div className="board-row">
<Square />
<Square />
<Square />
</div>
<div className="board-row">
<Square />
<Square />
<Square />
</div>
<div className="board-row">
<Square />
<Square />
<Square />
</div>
</>
);
}browser div-களுக்கு மாறாக, உங்கள் சொந்த component-கள் Board மற்றும் Square capital letter-ஆல் தொடங்க வேண்டும் என்பதை கவனியுங்கள்.
பார்ப்போம்:
ஓஹோ! முன்பு இருந்த numbered square-களை இழந்துவிட்டீர்கள். இப்போது ஒவ்வொரு square-மும் “1” என்று காட்டுகிறது. இதைச் சரிசெய்ய, ஒவ்வொரு square-க்கும் இருக்க வேண்டிய value-ஐ parent component (Board) இலிருந்து அதன் child (Square) க்கு pass செய்ய props பயன்படுத்துவீர்கள்.
Board-இலிருந்து pass செய்யப்போகும் value prop-ஐ read செய்ய Square component-ஐ update செய்யுங்கள்:
function Square({ value }) {
return <button className="square">1</button>;
}function Square({ value }) என்பது Square component-க்கு value என்ற prop pass செய்யலாம் என்பதைக் குறிக்கிறது.
இப்போது ஒவ்வொரு square-க்குள்ளும் 1-க்கு பதிலாக அந்த value-ஐ காட்ட விரும்புகிறீர்கள். இப்படிச் செய்து பாருங்கள்:
function Square({ value }) {
return <button className="square">value</button>;
}அச்சச்சோ, இது நீங்கள் விரும்பியது அல்ல:
உங்கள் component-இலிருந்து value என்ற JavaScript variable-ஐ render செய்ய விரும்பினீர்கள்; “value” என்ற வார்த்தையை அல்ல. JSX-இலிருந்து “JavaScript-க்குள் செல்ல” curly brace-கள் தேவை. JSX-இல் value சுற்றி curly brace-களை இவ்வாறு சேருங்கள்:
function Square({ value }) {
return <button className="square">{value}</button>;
}இப்போது காலியான board ஒன்றைப் பார்க்க வேண்டும்:
ஏனெனில் Board component, அது render செய்யும் ஒவ்வொரு Square component-க்கும் இன்னும் value prop-ஐ pass செய்யவில்லை. இதைச் சரிசெய்ய, Board component render செய்யும் ஒவ்வொரு Square component-க்கும் value prop-ஐச் சேர்ப்பீர்கள்:
export default function Board() {
return (
<>
<div className="board-row">
<Square value="1" />
<Square value="2" />
<Square value="3" />
</div>
<div className="board-row">
<Square value="4" />
<Square value="5" />
<Square value="6" />
</div>
<div className="board-row">
<Square value="7" />
<Square value="8" />
<Square value="9" />
</div>
</>
);
}இப்போது மீண்டும் எண்களைக் கொண்ட grid-ஐப் பார்க்க வேண்டும்:
உங்கள் updated code இதுபோல் இருக்க வேண்டும்:
function Square({ value }) { return <button className="square">{value}</button>; } export default function Board() { return ( <> <div className="board-row"> <Square value="1" /> <Square value="2" /> <Square value="3" /> </div> <div className="board-row"> <Square value="4" /> <Square value="5" /> <Square value="6" /> </div> <div className="board-row"> <Square value="7" /> <Square value="8" /> <Square value="9" /> </div> </> ); }
interactive component ஒன்றை உருவாக்குதல்
Square component-ஐ click செய்தால் அதில் X நிரம்புமாறு செய்வோம். Square-க்குள் handleClick என்ற function-ஐ declare செய்யுங்கள். பிறகு, Square return செய்யும் button JSX element-ன் props-க்கு onClick சேருங்கள்:
function Square({ value }) {
function handleClick() {
console.log('click செய்யப்பட்டது!');
}
return (
<button
className="square"
onClick={handleClick}
>
{value}
</button>
);
}இப்போது square-ஐ click செய்தால், CodeSandbox-இல் Browser பிரிவின் கீழே உள்ள Console tab-இல் "click செய்யப்பட்டது!" என்று log காண வேண்டும். square-ஐ ஒன்றுக்கு மேற்பட்ட முறை click செய்தால் "click செய்யப்பட்டது!" மீண்டும் log ஆகும். ஒரே message உடன் மீண்டும் மீண்டும் வரும் console log-கள் console-இல் மேலும் பல வரிகளை உருவாக்காது. அதற்கு பதிலாக, உங்கள் முதல் "click செய்யப்பட்டது!" log-க்கு அருகில் அதிகரிக்கும் counter ஒன்றைப் பார்ப்பீர்கள்.
அடுத்த படியாக, Square component தன்னை click செய்ததை “நினைவில்” வைத்து, “X” mark-ஆல் நிரம்ப வேண்டும். விஷயங்களை “நினைவில்” வைக்க component-கள் state-ஐப் பயன்படுத்துகின்றன.
React useState என்ற சிறப்பு function ஒன்றை வழங்குகிறது; component-இலிருந்து அதை call செய்து அது விஷயங்களை “நினைவில்” வைத்துக்கொள்ளச் செய்யலாம். Square-ன் தற்போதைய value-ஐ state-இல் சேமித்து, Square click செய்யப்படும் போது அதை மாற்றுவோம்.
file-ன் மேல் பகுதியில் useState-ஐ import செய்யுங்கள். Square component-இலிருந்து value prop-ஐ நீக்குங்கள். அதற்கு பதிலாக, Square தொடக்கத்தில் useState-ஐ call செய்யும் புதிய வரி ஒன்றைச் சேர்க்குங்கள். அது value என்ற state variable-ஐ return செய்யட்டும்:
import { useState } from 'react';
function Square() {
const [value, setValue] = useState(null);
function handleClick() {
//...value மதிப்பைச் சேமிக்கிறது; setValue மதிப்பை மாற்ற பயன்படுத்தக்கூடிய function. useState-க்கு pass செய்யப்பட்ட null, இந்த state variable-க்கான initial value ஆக பயன்படுத்தப்படுகிறது; எனவே இங்கே value ஆரம்பத்தில் null ஆக இருக்கும்.
Square component இனி props ஏற்காததால், Board component உருவாக்கும் ஒன்பது Square component-களிலிருந்தும் value prop-ஐ நீக்குவீர்கள்:
// ...
export default function Board() {
return (
<>
<div className="board-row">
<Square />
<Square />
<Square />
</div>
<div className="board-row">
<Square />
<Square />
<Square />
</div>
<div className="board-row">
<Square />
<Square />
<Square />
</div>
</>
);
}இப்போது click செய்தால் “X” காட்டும் வகையில் Square-ஐ மாற்றுவீர்கள். console.log("click செய்யப்பட்டது!"); event handler-ஐ setValue('X');-ஆல் மாற்றுங்கள். இப்போது உங்கள் Square component இதுபோல் இருக்கும்:
function Square() {
const [value, setValue] = useState(null);
function handleClick() {
setValue('X');
}
return (
<button
className="square"
onClick={handleClick}
>
{value}
</button>
);
}onClick handler-இலிருந்து இந்த set function-ஐ call செய்வதன் மூலம், அதன் <button> click செய்யப்படும் போதெல்லாம் அந்த Square-ஐ re-render செய்ய React-க்கு சொல்லுகிறீர்கள். update-க்குப் பிறகு, Square-ன் value 'X' ஆக இருக்கும், எனவே game board-இல் “X” காணப்படும். ஏதாவது Square-ஐ click செய்யுங்கள்; “X” தோன்ற வேண்டும்:
ஒவ்வொரு Square-க்கும் தனித்தனி state உள்ளது: ஒவ்வொரு Square-இல் சேமிக்கப்படும் value, மற்றவற்றிலிருந்து முற்றிலும் independent. ஒரு component-இல் set function-ஐ call செய்தால், அதன் உள்ளே உள்ள child component-களையும் React தானாக update செய்கிறது.
மேலுள்ள மாற்றங்களைச் செய்த பிறகு, உங்கள் code இதுபோல் இருக்கும்:
import { useState } from 'react'; function Square() { const [value, setValue] = useState(null); function handleClick() { setValue('X'); } return ( <button className="square" onClick={handleClick} > {value} </button> ); } export default function Board() { return ( <> <div className="board-row"> <Square /> <Square /> <Square /> </div> <div className="board-row"> <Square /> <Square /> <Square /> </div> <div className="board-row"> <Square /> <Square /> <Square /> </div> </> ); }
React Developer Tools
React DevTools உங்கள் React component-களின் props மற்றும் state-ஐச் சரிபார்க்க அனுமதிக்கிறது. CodeSandbox-இல் browser பிரிவின் கீழே React DevTools tab-ஐக் காணலாம்:
screen-இல் குறிப்பிட்ட component-ஐ inspect செய்ய, React DevTools-ன் இடது மேல் மூலையில் உள்ள button-ஐப் பயன்படுத்துங்கள்:
game-ஐ முடித்தல்
இந்த கட்டத்தில், உங்கள் tic-tac-toe game-க்கான அனைத்து அடிப்படை building block-களும் உள்ளன. முழுமையான game-க்கு, board-இல் மாறி மாறி “X” மற்றும் “O” வைக்க வேண்டும்; மேலும் வெற்றியாளரைத் தீர்மானிக்கும் வழி வேண்டும்.
state-ஐ மேலே தூக்குதல்
தற்போது, ஒவ்வொரு Square component-மும் game state-ன் ஒரு பகுதியை பராமரிக்கிறது. tic-tac-toe game-இல் வெற்றியாளரைச் சரிபார்க்க, Board எப்படியோ ஒன்பது Square component-களின் state-ஐ அறிந்திருக்க வேண்டும்.
அதை எப்படி அணுகுவீர்கள்? முதலில், Board ஒவ்வொரு Square-யிடமும் அதன் state-ஐ “கேட்க” வேண்டும் என்று நினைக்கலாம். React-இல் இந்த அணுகுமுறை தொழில்நுட்ப ரீதியாக சாத்தியமானதாயினும், code புரிந்துகொள்ள கடினமாகி, bug-களுக்கு எளிதில் உட்பட்டு, refactor செய்ய கடினமாக இருப்பதால் அதைத் தவிர்க்க பரிந்துரைக்கிறோம். அதற்கு பதிலாக, game state-ஐ ஒவ்வொரு Square-இலும் வைத்திருப்பதற்குப் பதிலாக parent Board component-இல் சேமிப்பதே சிறந்த அணுகுமுறை. ஒவ்வொரு Square-க்கும் எண் pass செய்தது போல, prop pass செய்வதன் மூலம் Board component ஒவ்வொரு Square-க்கும் என்ன காட்ட வேண்டும் என்று சொல்லலாம்.
பல children-இலிருந்து data சேகரிக்க, அல்லது இரண்டு child component-கள் ஒன்றுடன் ஒன்று தொடர்புகொள்ள வேண்டும் என்றால், shared state-ஐ அவற்றின் parent component-இல் declare செய்யுங்கள். parent component அந்த state-ஐ props மூலம் children-க்கு மீண்டும் pass செய்யலாம். இது child component-களை ஒன்றுக்கொன்றும் parent-உடும் sync-இல் வைத்திருக்கும்.
React component-கள் refactor செய்யப்படும் போது state-ஐ parent component-க்கு lift செய்வது பொதுவானது.
இந்த வாய்ப்பைப் பயன்படுத்தி அதை முயற்சி செய்வோம். ஒன்பது square-களுக்கு பொருந்தும் 9 null-கள் கொண்ட array-ஐ default ஆகக் கொண்ட squares என்ற state variable-ஐ declare செய்ய Board component-ஐ edit செய்யுங்கள்:
// ...
export default function Board() {
const [squares, setSquares] = useState(Array(9).fill(null));
return (
// ...
);
}Array(9).fill(null) ஒன்பது element-கள் கொண்ட array ஒன்றை உருவாக்கி, அவற்றை ஒவ்வொன்றாக null ஆக அமைக்கிறது. அதைச் சுற்றியுள்ள useState() call, ஆரம்பத்தில் அந்த array-ஆக அமைக்கப்படும் squares state variable-ஐ declare செய்கிறது. array-இல் உள்ள ஒவ்வொரு entry-யும் ஒரு square-ன் value-க்கு பொருந்தும். பின்னர் board-ஐ நிரப்பும்போது, squares array இதுபோல் இருக்கும்:
['O', null, 'X', 'X', 'X', 'O', 'O', null, null]இப்போது உங்கள் Board component, அது render செய்யும் ஒவ்வொரு Square-க்கும் value prop-ஐ கீழே pass செய்ய வேண்டும்:
export default function Board() {
const [squares, setSquares] = useState(Array(9).fill(null));
return (
<>
<div className="board-row">
<Square value={squares[0]} />
<Square value={squares[1]} />
<Square value={squares[2]} />
</div>
<div className="board-row">
<Square value={squares[3]} />
<Square value={squares[4]} />
<Square value={squares[5]} />
</div>
<div className="board-row">
<Square value={squares[6]} />
<Square value={squares[7]} />
<Square value={squares[8]} />
</div>
</>
);
}அடுத்து, Board component-இலிருந்து value prop-ஐப் பெற Square component-ஐ edit செய்வீர்கள். இதற்காக Square component-ன் சொந்த stateful value tracking-ஐயும் button-ன் onClick prop-ஐயும் நீக்க வேண்டும்:
function Square({value}) {
return <button className="square">{value}</button>;
}இந்த கட்டத்தில் காலியான tic-tac-toe board-ஐப் பார்க்க வேண்டும்:
மேலும் உங்கள் code இதுபோல் இருக்க வேண்டும்:
import { useState } from 'react'; function Square({ value }) { return <button className="square">{value}</button>; } export default function Board() { const [squares, setSquares] = useState(Array(9).fill(null)); return ( <> <div className="board-row"> <Square value={squares[0]} /> <Square value={squares[1]} /> <Square value={squares[2]} /> </div> <div className="board-row"> <Square value={squares[3]} /> <Square value={squares[4]} /> <Square value={squares[5]} /> </div> <div className="board-row"> <Square value={squares[6]} /> <Square value={squares[7]} /> <Square value={squares[8]} /> </div> </> ); }
இப்போது ஒவ்வொரு Square-க்கும் value prop கிடைக்கும்; அது 'X', 'O', அல்லது காலியான square-களுக்கு null ஆக இருக்கும்.
அடுத்து, ஒரு Square click செய்யப்படும் போது என்ன நடக்கிறது என்பதை மாற்ற வேண்டும். எந்த square-கள் நிரம்பியுள்ளன என்பதை இப்போது Board component பராமரிக்கிறது. Square மூலம் Board-ன் state-ஐ update செய்யும் வழி ஒன்றை உருவாக்க வேண்டும். state அதை define செய்யும் component-க்கு private ஆனதால், Square-இலிருந்து Board-ன் state-ஐ நேரடியாக update செய்ய முடியாது.
அதற்கு பதிலாக, Board component-இலிருந்து Square component-க்கு function ஒன்றை pass செய்வீர்கள்; square click செய்யப்படும் போது Square அந்த function-ஐ call செய்யும். Square component click செய்யப்படும் போது call செய்யும் function-இலிருந்து தொடங்குவீர்கள். அந்த function-ஐ onSquareClick என்று அழைப்பீர்கள்:
function Square({ value }) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
);
}அடுத்து, Square component-ன் props-க்கு onSquareClick function-ஐச் சேர்ப்பீர்கள்:
function Square({ value, onSquareClick }) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
);
}இப்போது onSquareClick prop-ஐ Board component-இல் handleClick என்று பெயரிடப் போகும் function-க்கு connect செய்வீர்கள். onSquareClick-ஐ handleClick-க்கு connect செய்ய, முதல் Square component-ன் onSquareClick prop-க்கு function ஒன்றை pass செய்வீர்கள்:
export default function Board() {
const [squares, setSquares] = useState(Array(9).fill(null));
return (
<>
<div className="board-row">
<Square value={squares[0]} onSquareClick={handleClick} />
//...
);
}கடைசியாக, உங்கள் board-ன் state-ஐ வைத்திருக்கும் squares array-ஐ update செய்ய Board component-க்குள் handleClick function-ஐ define செய்வீர்கள்:
export default function Board() {
const [squares, setSquares] = useState(Array(9).fill(null));
function handleClick() {
const nextSquares = squares.slice();
nextSquares[0] = "X";
setSquares(nextSquares);
}
return (
// ...
)
}handleClick function JavaScript slice() Array method மூலம் squares array-ன் copy (nextSquares) ஒன்றை உருவாக்குகிறது. பிறகு, முதல் ([0] index) square-க்கு X சேர்க்க nextSquares array-ஐ handleClick update செய்கிறது.
setSquares function-ஐ call செய்வது, component-ன் state மாறிவிட்டது என்பதை React-க்கு தெரிவிக்கிறது. இது squares state-ஐப் பயன்படுத்தும் component-களையும் (Board), அதன் child component-களையும் (board-ஐ உருவாக்கும் Square component-கள்) re-render செய்ய trigger செய்யும்.
இப்போது board-க்கு X-களைச் சேர்க்கலாம்… ஆனால் மேலே இடதுபுற square-க்கு மட்டும். உங்கள் handleClick function மேல் இடதுபுற square (0) index-ஐ update செய்ய hardcode செய்யப்பட்டுள்ளது. எந்த square-யையும் update செய்ய முடியும் வகையில் handleClick-ஐ update செய்வோம். update செய்ய வேண்டிய square-ன் index-ஐ எடுக்க handleClick function-க்கு i என்ற argument சேர்க்கவும்:
export default function Board() {
const [squares, setSquares] = useState(Array(9).fill(null));
function handleClick(i) {
const nextSquares = squares.slice();
nextSquares[i] = "X";
setSquares(nextSquares);
}
return (
// ...
)
}அடுத்து, அந்த i-யை handleClick-க்கு pass செய்ய வேண்டும். square-ன் onSquareClick prop-ஐ நேரடியாக JSX-இல் handleClick(0) ஆக அமைக்க முயற்சிக்கலாம்; ஆனால் அது வேலை செய்யாது:
<Square value={squares[0]} onSquareClick={handleClick(0)} />இது ஏன் வேலை செய்யாது என்பதைப் பார்ப்போம். handleClick(0) call board component rendering-ன் ஒரு பகுதியாகிவிடும். handleClick(0) setSquares-ஐ call செய்து board component-ன் state-ஐ மாற்றுவதால், முழு board component மீண்டும் re-render ஆகும். ஆனால் இது handleClick(0)-ஐ மீண்டும் ஓட்டும்; இதனால் infinite loop ஏற்படும்:
இந்த பிரச்சினை முன்பு ஏன் நடக்கவில்லை?
நீங்கள் onSquareClick={handleClick} pass செய்தபோது, handleClick function-ஐ prop ஆக கீழே pass செய்தீர்கள். அதை call செய்யவில்லை! ஆனால் இப்போது அந்த function-ஐ உடனே call செய்கிறீர்கள்—handleClick(0)-இல் உள்ள parenthesis-களை கவனியுங்கள்—அதனால் அது மிக விரைவாக ஓடுகிறது. பயனர் click செய்யும் வரை handleClick-ஐ call செய்ய வேண்டாம்!
handleClick(0)-ஐ call செய்யும் handleFirstSquareClick போன்ற function, handleClick(1)-ஐ call செய்யும் handleSecondSquareClick போன்ற function என உருவாக்கி இதைச் சரிசெய்யலாம். இந்த function-களை onSquareClick={handleFirstSquareClick} போன்ற props ஆக கீழே pass செய்வீர்கள் (call செய்வதில்லை). இது infinite loop-ஐத் தீர்க்கும்.
ஆனால், ஒன்பது வேறு function-களை define செய்து ஒவ்வொன்றுக்கும் பெயர் கொடுப்பது மிகவும் verbose. அதற்கு பதிலாக இதைச் செய்வோம்:
export default function Board() {
// ...
return (
<>
<div className="board-row">
<Square value={squares[0]} onSquareClick={() => handleClick(0)} />
// ...
);
}புதிய () => syntax-ஐ கவனியுங்கள். இங்கே, () => handleClick(0) என்பது arrow function; function-களை define செய்யும் குறுகிய வழி. square click செய்யப்படும் போது, => “arrow”-க்கு பின் உள்ள code ஓடி, handleClick(0)-ஐ call செய்யும்.
இப்போது, pass செய்யும் arrow function-களிலிருந்து handleClick-ஐ call செய்ய மற்ற எட்டு square-களையும் update செய்ய வேண்டும். handleClick-ன் ஒவ்வொரு call-க்கும் argument சரியான square-ன் index-க்கு பொருந்துவதை உறுதிசெய்யுங்கள்:
export default function Board() {
// ...
return (
<>
<div className="board-row">
<Square value={squares[0]} onSquareClick={() => handleClick(0)} />
<Square value={squares[1]} onSquareClick={() => handleClick(1)} />
<Square value={squares[2]} onSquareClick={() => handleClick(2)} />
</div>
<div className="board-row">
<Square value={squares[3]} onSquareClick={() => handleClick(3)} />
<Square value={squares[4]} onSquareClick={() => handleClick(4)} />
<Square value={squares[5]} onSquareClick={() => handleClick(5)} />
</div>
<div className="board-row">
<Square value={squares[6]} onSquareClick={() => handleClick(6)} />
<Square value={squares[7]} onSquareClick={() => handleClick(7)} />
<Square value={squares[8]} onSquareClick={() => handleClick(8)} />
</div>
</>
);
};இப்போது board-இல் எந்த square-யையும் click செய்து மீண்டும் X-களைச் சேர்க்கலாம்:
ஆனால் இந்த முறை state management அனைத்தும் Board component மூலம் கையாளப்படுகிறது!
உங்கள் code இதுபோல் இருக்க வேண்டும்:
import { useState } from 'react'; function Square({ value, onSquareClick }) { return ( <button className="square" onClick={onSquareClick}> {value} </button> ); } export default function Board() { const [squares, setSquares] = useState(Array(9).fill(null)); function handleClick(i) { const nextSquares = squares.slice(); nextSquares[i] = 'X'; setSquares(nextSquares); } return ( <> <div className="board-row"> <Square value={squares[0]} onSquareClick={() => handleClick(0)} /> <Square value={squares[1]} onSquareClick={() => handleClick(1)} /> <Square value={squares[2]} onSquareClick={() => handleClick(2)} /> </div> <div className="board-row"> <Square value={squares[3]} onSquareClick={() => handleClick(3)} /> <Square value={squares[4]} onSquareClick={() => handleClick(4)} /> <Square value={squares[5]} onSquareClick={() => handleClick(5)} /> </div> <div className="board-row"> <Square value={squares[6]} onSquareClick={() => handleClick(6)} /> <Square value={squares[7]} onSquareClick={() => handleClick(7)} /> <Square value={squares[8]} onSquareClick={() => handleClick(8)} /> </div> </> ); }
இப்போது state handling Board component-இல் இருப்பதால், parent Board component child Square component-களுக்கு அவை சரியாகக் காட்டப்பட props pass செய்கிறது. Square-ஐ click செய்யும்போது, child Square component இப்போது board state-ஐ update செய்ய parent Board component-யிடம் கேட்கிறது. Board-ன் state மாறும்போது, Board component மற்றும் ஒவ்வொரு child Square-மும் தானாக re-render ஆகும். எல்லா square-களின் state-ஐ Board component-இல் வைத்திருப்பது, பின்னர் வெற்றியாளரைத் தீர்மானிக்க அனுமதிக்கும்.
பயனர் உங்கள் board-இன் மேல் இடது square-ஐ click செய்து அதில் X சேர்க்கும்போது என்ன நடக்கிறது என்பதை recap செய்வோம்:
- மேல் இடது square-ஐ click செய்வது,
Square-இலிருந்துbuttonஅதன்onClickprop ஆக பெற்ற function-ஐ ஓட்டுகிறது.Squarecomponent அந்த function-ஐBoard-இலிருந்து அதன்onSquareClickprop ஆகப் பெற்றது.Boardcomponent அந்த function-ஐ நேரடியாக JSX-இல் define செய்தது. அது0என்ற argument உடன்handleClick-ஐ call செய்கிறது. handleClickargument-ஐ (0) பயன்படுத்திsquaresarray-ன் முதல் element-ஐnull-இலிருந்துXஆக update செய்கிறது.Boardcomponent-ன்squaresstate update ஆனதால்,Boardமற்றும் அதன் எல்லா children-களும் re-render ஆகின்றன. இதனால் index0கொண்டSquarecomponent-ன்valuepropnull-இலிருந்துXஆக மாறுகிறது.
இறுதியில், click செய்த பிறகு மேல் இடது square காலியாக இருந்ததிலிருந்து X கொண்டதாக மாறியிருப்பதை பயனர் காண்கிறார்.
immutability ஏன் முக்கியம்
handleClick-இல், existing array-ஐ modify செய்வதற்கு பதிலாக squares array-ன் copy ஒன்றை உருவாக்க .slice() call செய்வதை கவனியுங்கள். ஏன் என்பதை விளக்க, immutability மற்றும் அதை கற்றுக்கொள்வது ஏன் முக்கியம் என்பதைப் பற்றி பேச வேண்டும்.
data-வை மாற்ற பொதுவாக இரண்டு அணுகுமுறைகள் உள்ளன. முதல் அணுகுமுறை data-வின் value-களை நேரடியாக மாற்றி data-வை mutate செய்வது. இரண்டாவது அணுகுமுறை, விரும்பிய மாற்றங்களைக் கொண்ட புதிய copy-ஆல் data-வை மாற்றுவது. squares array-ஐ mutate செய்தால் அது எப்படி இருக்கும்:
const squares = [null, null, null, null, null, null, null, null, null];
squares[0] = 'X';
// Now `squares` is ["X", null, null, null, null, null, null, null, null];squares array-ஐ mutate செய்யாமல் data-வை மாற்றினால் அது எப்படி இருக்கும்:
const squares = [null, null, null, null, null, null, null, null, null];
const nextSquares = ['X', null, null, null, null, null, null, null, null];
// Now `squares` is unchanged, but `nextSquares` first element is 'X' rather than `null`முடிவு ஒன்றுதான்; ஆனால் நேரடியாக mutate செய்யாமல் (அடிப்படை data-வை மாற்றாமல்) இருப்பதால் பல நன்மைகள் கிடைக்கும்.
Immutability சிக்கலான feature-களை implement செய்வதை மிகவும் உதவுகிறது. இந்த tutorial-இல் பின்னர், game history-யை review செய்து கடந்த நகர்வுகளுக்கு “jump back” செய்ய அனுமதிக்கும் “time travel” feature-ஐ implement செய்வீர்கள். இந்த functionality game-களுக்கே மட்டும் அல்ல; குறிப்பிட்ட action-களை undo மற்றும் redo செய்யும் திறன் app-களில் பொதுவான தேவையாகும். direct data mutation-ஐத் தவிர்ப்பது data-வின் முந்தைய version-களை intact ஆக வைத்திருந்து பின்னர் மீண்டும் பயன்படுத்த உதவும்.
Immutability-க்கு இன்னொரு நன்மையும் உள்ளது. இயல்பாக, parent component-ன் state மாறும்போது எல்லா child component-களும் தானாக re-render ஆகும். மாற்றத்தால் பாதிக்கப்படாத child component-களும் இதில் அடங்கும். re-rendering தனியாக பயனருக்கு தெரியாது என்றாலும் (அதைத் தவிர்க்க நீங்கள் actively முயற்சிக்க வேண்டியதில்லை!), performance காரணங்களுக்காக தெளிவாக பாதிக்கப்படாத tree-ன் ஒரு பகுதி re-render ஆகாமல் skip செய்ய விரும்பலாம். data மாறியதா இல்லையா என்பதை component-கள் compare செய்வதை immutability மிகவும் cheap ஆக்குகிறது. component-ஐ எப்போது re-render செய்ய வேண்டும் என்பதை React எப்படி தேர்வு செய்கிறது என்பதை the memo API reference-இல் மேலும் அறியலாம்.
மாறிமாறி விளையாடுதல்
இந்த tic-tac-toe game-இல் பெரிய குறையை இப்போது சரிசெய்ய நேரம்: board-இல் “O”களை குறிக்க முடியவில்லை.
முதல் நகர்வை இயல்பாக “X” ஆக அமைப்பீர்கள். இதைப் பின்தொடர Board component-க்கு இன்னொரு state பகுதியைச் சேர்ப்போம்:
function Board() {
const [xIsNext, setXIsNext] = useState(true);
const [squares, setSquares] = useState(Array(9).fill(null));
// ...
}ஒவ்வொரு முறை வீரர் நகரும்போது, அடுத்து எந்த வீரர் செல்ல வேண்டும் என்பதைத் தீர்மானிக்க xIsNext (boolean) flip செய்யப்படும்; game state சேமிக்கப்படும். xIsNext மதிப்பை flip செய்ய Board-ன் handleClick function-ஐ update செய்வீர்கள்:
export default function Board() {
const [xIsNext, setXIsNext] = useState(true);
const [squares, setSquares] = useState(Array(9).fill(null));
function handleClick(i) {
const nextSquares = squares.slice();
if (xIsNext) {
nextSquares[i] = "X";
} else {
nextSquares[i] = "O";
}
setSquares(nextSquares);
setXIsNext(!xIsNext);
}
return (
//...
);
}இப்போது, வேறு square-களை click செய்தால், அவை தேவையானபடி X மற்றும் O இடையே மாறி மாறி வருவதாக இருக்கும்!
ஆனால் காத்திருங்கள், ஒரு பிரச்சினை உள்ளது. அதே square-ஐ பல முறை click செய்து பாருங்கள்:
X-ஐ O overwrite செய்கிறது! இது game-க்கு மிகவும் சுவாரஸ்யமான twist சேர்த்தாலும், இப்போது original rules-ஐப் பின்பற்றுவோம்.
ஒரு square-ஐ X அல்லது O என குறிக்கும் முன், அந்த square-ல் ஏற்கனவே X அல்லது O value உள்ளதா என்று சரிபார்க்கவில்லை. இதை early return செய்வதன் மூலம் சரிசெய்யலாம். square-ல் ஏற்கனவே X அல்லது O உள்ளதா என்று சரிபார்ப்பீர்கள். square ஏற்கனவே நிரம்பியிருந்தால், board state-ஐ update செய்ய முயற்சிக்கும் முன்பே handleClick function-இல் return செய்வீர்கள்.
function handleClick(i) {
if (squares[i]) {
return;
}
const nextSquares = squares.slice();
//...
}இப்போது காலியான square-களில் மட்டுமே X அல்லது O சேர்க்க முடியும்! இந்த கட்டத்தில் உங்கள் code இவ்வாறு இருக்க வேண்டும்:
import { useState } from 'react'; function Square({value, onSquareClick}) { return ( <button className="square" onClick={onSquareClick}> {value} </button> ); } export default function Board() { const [xIsNext, setXIsNext] = useState(true); const [squares, setSquares] = useState(Array(9).fill(null)); function handleClick(i) { if (squares[i]) { return; } const nextSquares = squares.slice(); if (xIsNext) { nextSquares[i] = 'X'; } else { nextSquares[i] = 'O'; } setSquares(nextSquares); setXIsNext(!xIsNext); } return ( <> <div className="board-row"> <Square value={squares[0]} onSquareClick={() => handleClick(0)} /> <Square value={squares[1]} onSquareClick={() => handleClick(1)} /> <Square value={squares[2]} onSquareClick={() => handleClick(2)} /> </div> <div className="board-row"> <Square value={squares[3]} onSquareClick={() => handleClick(3)} /> <Square value={squares[4]} onSquareClick={() => handleClick(4)} /> <Square value={squares[5]} onSquareClick={() => handleClick(5)} /> </div> <div className="board-row"> <Square value={squares[6]} onSquareClick={() => handleClick(6)} /> <Square value={squares[7]} onSquareClick={() => handleClick(7)} /> <Square value={squares[8]} onSquareClick={() => handleClick(8)} /> </div> </> ); }
வெற்றியாளரை அறிவித்தல்
இப்போது வீரர்கள் மாறி மாறி விளையாட முடியும்; game வென்றுவிட்டது, மேலும் turn எதுவும் இல்லை என்பதை காட்ட விரும்புவீர்கள். இதைச் செய்ய, 9 square-களின் array-ஐ எடுத்துக்கொண்டு வெற்றியாளரைச் சரிபார்த்து, பொருத்தமானபடி 'X', 'O', அல்லது null return செய்யும் calculateWinner என்ற helper function-ஐச் சேர்ப்பீர்கள். calculateWinner function பற்றி அதிகம் கவலைப்பட வேண்டாம்; அது React-க்கு குறிப்பிட்டதல்ல:
export default function Board() {
//...
}
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;
}ஒரு வீரர் வென்றாரா என்பதைச் சரிபார்க்க, Board component-ன் handleClick function-இல் calculateWinner(squares)-ஐ call செய்வீர்கள். பயனர் ஏற்கனவே X அல்லது O உள்ள square-ஐ click செய்தாரா என்று சரிபார்ப்பதுடன் இதையும் ஒரே நேரத்தில் செய்யலாம். இரு சூழல்களிலும் early return செய்ய விரும்புகிறோம்:
function handleClick(i) {
if (squares[i] || calculateWinner(squares)) {
return;
}
const nextSquares = squares.slice();
//...
}game முடிந்ததை வீரர்கள் அறிய, “வெற்றி பெற்றவர்: X” அல்லது “வெற்றி பெற்றவர்: O” போன்ற text-ஐக் காட்டலாம். அதற்காக Board component-க்கு status section ஒன்றைச் சேர்ப்பீர்கள். game முடிந்திருந்தால் status வெற்றியாளரை காட்டும்; game தொடர்ந்துகொண்டிருந்தால் அடுத்து எந்த வீரரின் turn என்று காட்டும்:
export default function Board() {
// ...
const winner = calculateWinner(squares);
let status;
if (winner) {
status = "வெற்றி பெற்றவர்: " + winner;
} else {
status = "அடுத்த வீரர்: " + (xIsNext ? "X" : "O");
}
return (
<>
<div className="status">{status}</div>
<div className="board-row">
// ...
)
}வாழ்த்துகள்! இப்போது உங்களிடம் வேலை செய்யும் tic-tac-toe game உள்ளது. அதோடு React-ன் அடிப்படைகளையும் இப்போது கற்றுக்கொண்டீர்கள். எனவே இங்கே உண்மையான வெற்றியாளர் நீங்கள்தான். code இதுபோல் இருக்க வேண்டும்:
import { useState } from 'react'; function Square({value, onSquareClick}) { return ( <button className="square" onClick={onSquareClick}> {value} </button> ); } export default function Board() { const [xIsNext, setXIsNext] = useState(true); const [squares, setSquares] = useState(Array(9).fill(null)); function handleClick(i) { if (calculateWinner(squares) || squares[i]) { return; } const nextSquares = squares.slice(); if (xIsNext) { nextSquares[i] = 'X'; } else { nextSquares[i] = 'O'; } setSquares(nextSquares); setXIsNext(!xIsNext); } const winner = calculateWinner(squares); let status; if (winner) { status = 'வெற்றி பெற்றவர்: ' + winner; } else { status = 'அடுத்த வீரர்: ' + (xIsNext ? 'X' : 'O'); } return ( <> <div className="status">{status}</div> <div className="board-row"> <Square value={squares[0]} onSquareClick={() => handleClick(0)} /> <Square value={squares[1]} onSquareClick={() => handleClick(1)} /> <Square value={squares[2]} onSquareClick={() => handleClick(2)} /> </div> <div className="board-row"> <Square value={squares[3]} onSquareClick={() => handleClick(3)} /> <Square value={squares[4]} onSquareClick={() => handleClick(4)} /> <Square value={squares[5]} onSquareClick={() => handleClick(5)} /> </div> <div className="board-row"> <Square value={squares[6]} onSquareClick={() => handleClick(6)} /> <Square value={squares[7]} onSquareClick={() => handleClick(7)} /> <Square value={squares[8]} onSquareClick={() => handleClick(8)} /> </div> </> ); } 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; }
time travel சேர்த்தல்
இறுதி exercise ஆக, game-இல் முந்தைய நகர்வுகளுக்கு “காலத்தில் பின்செல்ல” முடியுமாறு செய்வோம்.
நகர்வுகளின் history-யை சேமித்தல்
squares array-ஐ mutate செய்திருந்தால், time travel implement செய்வது மிகவும் கடினமாக இருக்கும்.
ஆனால் ஒவ்வொரு move-க்குப் பிறகும் squares array-ன் புதிய copy-ஐ உருவாக்க slice()-ஐப் பயன்படுத்தி, அதை immutable ஆக நடத்தினீர்கள். இதனால் squares array-ன் முந்தைய ஒவ்வொரு version-ஐயும் சேமித்து, ஏற்கனவே நடந்த turn-களுக்கிடையே navigate செய்ய முடியும்.
முந்தைய squares array-களை history என்ற மற்றொரு array-இல் சேமிப்பீர்கள்; அதை புதிய state variable ஆக சேமிப்பீர்கள். history array முதல் move-இலிருந்து கடைசி move வரை எல்லா board state-களையும் பிரதிநிதித்துவப்படுத்துகிறது; அது இதுபோன்ற shape கொண்டது:
[
// Before first move
[null, null, null, null, null, null, null, null, null],
// After first move
[null, null, null, null, 'X', null, null, null, null],
// After second move
[null, null, null, null, 'X', null, null, null, 'O'],
// ...
]state-ஐ மீண்டும் மேலே தூக்குதல்
இப்போது முந்தைய move-களின் பட்டியலைக் காட்ட Game என்ற புதிய top-level component எழுதுவீர்கள். முழு game history-யையும் கொண்ட history state-ஐ அங்கே வைப்பீர்கள்.
history state-ஐ Game component-இல் வைப்பது, அதன் child Board component-இலிருந்து squares state-ஐ நீக்க அனுமதிக்கும். Square component-இலிருந்து Board component-க்கு “state-ஐ மேலே தூக்கியது” போலவே, இப்போது Board-இலிருந்து top-level Game component-க்கு அதை lift செய்வீர்கள். இதனால் Board-ன் data மீது Game component-க்கு முழு control கிடைக்கும்; மேலும் history-இலிருந்து முந்தைய turn-களை render செய்ய Board-க்கு சொல்ல முடியும்.
முதலில், export default உடன் Game component ஒன்றைச் சேர்க்குங்கள். அது Board component-யையும் சில markup-ஐயும் render செய்யட்டும்:
function Board() {
// ...
}
export default function Game() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<ol>{/*TODO*/}</ol>
</div>
</div>
);
}function Board() { declaration-க்கு முன் இருந்த export default keyword-களை நீக்கி, அவற்றை function Game() { declaration-க்கு முன் சேர்க்கிறீர்கள் என்பதை கவனியுங்கள். இது உங்கள் index.js file-க்கு Board component-க்கு பதிலாக Game component-ஐ top-level component ஆக பயன்படுத்த சொல்லுகிறது. Game component return செய்யும் கூடுதல் div-கள், பின்னர் board-க்கு சேர்க்கப் போகும் game தகவல்களுக்கு இடம் செய்கின்றன.
அடுத்து எந்த வீரர் என்பதையும் move history-யையும் track செய்ய Game component-க்கு சில state சேர்க்கவும்:
export default function Game() {
const [xIsNext, setXIsNext] = useState(true);
const [history, setHistory] = useState([Array(9).fill(null)]);
// ...[Array(9).fill(null)] என்பது ஒரே item கொண்ட array; அந்த item தானே 9 null-கள் கொண்ட array என்பதை கவனியுங்கள்.
தற்போதைய move-க்கான square-களை render செய்ய, history-இலிருந்து கடைசி squares array-ஐ read செய்ய விரும்புவீர்கள். இதற்கு useState தேவையில்லை; rendering-இன் போது இதை கணக்கிட போதுமான தகவல் உங்களிடம் ஏற்கனவே உள்ளது:
export default function Game() {
const [xIsNext, setXIsNext] = useState(true);
const [history, setHistory] = useState([Array(9).fill(null)]);
const currentSquares = history[history.length - 1];
// ...அடுத்து, game-ஐ update செய்ய Board component call செய்யும் handlePlay function-ஐ Game component-க்குள் உருவாக்குங்கள். xIsNext, currentSquares, மற்றும் handlePlay-ஐ Board component-க்கு props ஆக pass செய்யுங்கள்:
export default function Game() {
const [xIsNext, setXIsNext] = useState(true);
const [history, setHistory] = useState([Array(9).fill(null)]);
const currentSquares = history[history.length - 1];
function handlePlay(nextSquares) {
// TODO
}
return (
<div className="game">
<div className="game-board">
<Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
//...
)
}Board component அது பெறும் props மூலம் முழுமையாக controlled ஆகட்டும். Board component மூன்று props எடுக்குமாறு மாற்றுங்கள்: xIsNext, squares, மற்றும் வீரர் move செய்தபோது updated squares array உடன் Board call செய்யக்கூடிய புதிய onPlay function. அடுத்து, useState-ஐ call செய்யும் Board function-ன் முதல் இரண்டு வரிகளை நீக்குங்கள்:
function Board({ xIsNext, squares, onPlay }) {
function handleClick(i) {
//...
}
// ...
}இப்போது பயனர் square-ஐ click செய்தபோது Game component Board-ஐ update செய்ய முடியும் வகையில், Board component-இன் handleClick-இல் உள்ள setSquares மற்றும் setXIsNext call-களை உங்கள் புதிய onPlay function-க்கு செய்யும் ஒரே call-ஆல் மாற்றுங்கள்:
function Board({ xIsNext, squares, onPlay }) {
function handleClick(i) {
if (calculateWinner(squares) || squares[i]) {
return;
}
const nextSquares = squares.slice();
if (xIsNext) {
nextSquares[i] = "X";
} else {
nextSquares[i] = "O";
}
onPlay(nextSquares);
}
//...
}Board component, Game component pass செய்யும் props மூலம் முழுமையாக controlled ஆகிறது. game மீண்டும் வேலை செய்ய Game component-இல் handlePlay function-ஐ implement செய்ய வேண்டும்.
handlePlay call செய்யப்பட்டால் அது என்ன செய்ய வேண்டும்? Board முன்பு updated array உடன் setSquares-ஐ call செய்ததை நினைவில் கொள்ளுங்கள்; இப்போது அது updated squares array-ஐ onPlay-க்கு pass செய்கிறது.
re-render trigger செய்ய handlePlay function Game-ன் state-ஐ update செய்ய வேண்டும்; ஆனால் call செய்யக்கூடிய setSquares function இனி இல்லை—இந்த தகவலை சேமிக்க இப்போது history state variable-ஐப் பயன்படுத்துகிறீர்கள். updated squares array-ஐ புதிய history entry ஆக append செய்து history-ஐ update செய்ய விரும்புவீர்கள். Board முன்பு செய்தது போல xIsNext-ஐயும் toggle செய்ய வேண்டும்:
export default function Game() {
//...
function handlePlay(nextSquares) {
setHistory([...history, nextSquares]);
setXIsNext(!xIsNext);
}
//...
}இங்கே, [...history, nextSquares] என்பது history-இல் உள்ள எல்லா item-களையும் தொடர்ந்து nextSquares-ஐக் கொண்ட புதிய array ஒன்றை உருவாக்குகிறது. (...history spread syntax-ஐ “history-இல் உள்ள எல்லா item-களையும் enumerate செய்” என்று படிக்கலாம்.)
உதாரணமாக, history [[null,null,null], ["X",null,null]] ஆகவும் nextSquares ["X",null,"O"] ஆகவும் இருந்தால், புதிய [...history, nextSquares] array [[null,null,null], ["X",null,null], ["X",null,"O"]] ஆக இருக்கும்.
இந்த கட்டத்தில், state-ஐ Game component-இல் வாழுமாறு நகர்த்திவிட்டீர்கள்; refactor-க்கு முன்பு இருந்தது போல UI முழுமையாக வேலை செய்ய வேண்டும். இந்த கட்டத்தில் code இவ்வாறு இருக்க வேண்டும்:
import { useState } from 'react'; function Square({ value, onSquareClick }) { return ( <button className="square" onClick={onSquareClick}> {value} </button> ); } function Board({ xIsNext, squares, onPlay }) { function handleClick(i) { if (calculateWinner(squares) || squares[i]) { return; } const nextSquares = squares.slice(); if (xIsNext) { nextSquares[i] = 'X'; } else { nextSquares[i] = 'O'; } onPlay(nextSquares); } const winner = calculateWinner(squares); let status; if (winner) { status = 'வெற்றி பெற்றவர்: ' + winner; } else { status = 'அடுத்த வீரர்: ' + (xIsNext ? 'X' : 'O'); } return ( <> <div className="status">{status}</div> <div className="board-row"> <Square value={squares[0]} onSquareClick={() => handleClick(0)} /> <Square value={squares[1]} onSquareClick={() => handleClick(1)} /> <Square value={squares[2]} onSquareClick={() => handleClick(2)} /> </div> <div className="board-row"> <Square value={squares[3]} onSquareClick={() => handleClick(3)} /> <Square value={squares[4]} onSquareClick={() => handleClick(4)} /> <Square value={squares[5]} onSquareClick={() => handleClick(5)} /> </div> <div className="board-row"> <Square value={squares[6]} onSquareClick={() => handleClick(6)} /> <Square value={squares[7]} onSquareClick={() => handleClick(7)} /> <Square value={squares[8]} onSquareClick={() => handleClick(8)} /> </div> </> ); } export default function Game() { const [xIsNext, setXIsNext] = useState(true); const [history, setHistory] = useState([Array(9).fill(null)]); const currentSquares = history[history.length - 1]; function handlePlay(nextSquares) { setHistory([...history, nextSquares]); setXIsNext(!xIsNext); } return ( <div className="game"> <div className="game-board"> <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} /> </div> <div className="game-info"> <ol>{/*TODO*/}</ol> </div> </div> ); } 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; }
முந்தைய move-களை காட்டுதல்
tic-tac-toe game-ன் history-யை நீங்கள் record செய்கிறீர்கள் என்பதால், இப்போது வீரருக்கு முந்தைய move-களின் பட்டியலைக் காட்டலாம்.
<button> போன்ற React element-கள் சாதாரண JavaScript object-கள்; அவற்றை உங்கள் application-இல் pass செய்யலாம். React-இல் பல item-களை render செய்ய, React element-களின் array-ஐப் பயன்படுத்தலாம்.
state-இல் ஏற்கனவே history move-களின் array உள்ளது, எனவே இப்போது அதை React element-களின் array-ஆக transform செய்ய வேண்டும். JavaScript-இல் ஒரு array-ஐ மற்றொரு array-ஆக transform செய்ய, array map method-ஐப் பயன்படுத்தலாம்:
[1, 2, 3].map((x) => x * 2) // [2, 4, 6]உங்கள் move history-யை screen-இல் உள்ள button-களை பிரதிநிதித்துவப்படுத்தும் React element-களாக transform செய்ய map-ஐப் பயன்படுத்தி, முந்தைய move-களுக்கு “jump” செய்ய button பட்டியலைக் காட்டுவீர்கள். Game component-இல் history மீது map செய்வோம்:
export default function Game() {
const [xIsNext, setXIsNext] = useState(true);
const [history, setHistory] = useState([Array(9).fill(null)]);
const currentSquares = history[history.length - 1];
function handlePlay(nextSquares) {
setHistory([...history, nextSquares]);
setXIsNext(!xIsNext);
}
function jumpTo(nextMove) {
// TODO
}
const moves = history.map((squares, move) => {
let description;
if (move > 0) {
description = 'நகர்வு #' + move + '-க்கு செல்';
} else {
description = 'game தொடக்கத்துக்கு செல்';
}
return (
<li>
<button onClick={() => jumpTo(move)}>{description}</button>
</li>
);
});
return (
<div className="game">
<div className="game-board">
<Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
</div>
<div className="game-info">
<ol>{moves}</ol>
</div>
</div>
);
}உங்கள் code எப்படி இருக்க வேண்டும் என்பதை கீழே பார்க்கலாம். developer tools console-இல் இதுபோன்ற error ஒன்றைக் காண வேண்டும் என்பதை கவனியுங்கள்:
இந்த error-ஐ அடுத்த பிரிவில் சரிசெய்வீர்கள்.
import { useState } from 'react'; function Square({ value, onSquareClick }) { return ( <button className="square" onClick={onSquareClick}> {value} </button> ); } function Board({ xIsNext, squares, onPlay }) { function handleClick(i) { if (calculateWinner(squares) || squares[i]) { return; } const nextSquares = squares.slice(); if (xIsNext) { nextSquares[i] = 'X'; } else { nextSquares[i] = 'O'; } onPlay(nextSquares); } const winner = calculateWinner(squares); let status; if (winner) { status = 'வெற்றி பெற்றவர்: ' + winner; } else { status = 'அடுத்த வீரர்: ' + (xIsNext ? 'X' : 'O'); } return ( <> <div className="status">{status}</div> <div className="board-row"> <Square value={squares[0]} onSquareClick={() => handleClick(0)} /> <Square value={squares[1]} onSquareClick={() => handleClick(1)} /> <Square value={squares[2]} onSquareClick={() => handleClick(2)} /> </div> <div className="board-row"> <Square value={squares[3]} onSquareClick={() => handleClick(3)} /> <Square value={squares[4]} onSquareClick={() => handleClick(4)} /> <Square value={squares[5]} onSquareClick={() => handleClick(5)} /> </div> <div className="board-row"> <Square value={squares[6]} onSquareClick={() => handleClick(6)} /> <Square value={squares[7]} onSquareClick={() => handleClick(7)} /> <Square value={squares[8]} onSquareClick={() => handleClick(8)} /> </div> </> ); } export default function Game() { const [xIsNext, setXIsNext] = useState(true); const [history, setHistory] = useState([Array(9).fill(null)]); const currentSquares = history[history.length - 1]; function handlePlay(nextSquares) { setHistory([...history, nextSquares]); setXIsNext(!xIsNext); } function jumpTo(nextMove) { // TODO } const moves = history.map((squares, move) => { let description; if (move > 0) { description = 'நகர்வு #' + move + '-க்கு செல்'; } else { description = 'game தொடக்கத்துக்கு செல்'; } return ( <li> <button onClick={() => jumpTo(move)}>{description}</button> </li> ); }); return ( <div className="game"> <div className="game-board"> <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} /> </div> <div className="game-info"> <ol>{moves}</ol> </div> </div> ); } 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; }
map-க்கு pass செய்த function-க்குள் history array வழியாக iterate செய்யும்போது, squares argument history-ன் ஒவ்வொரு element வழியாகவும், move argument ஒவ்வொரு array index வழியாகவும் செல்கிறது: 0, 1, 2, …. (பெரும்பாலான சூழல்களில் actual array element-கள் தேவைப்படும்; ஆனால் move பட்டியலை render செய்ய index-கள் மட்டுமே தேவை.)
tic-tac-toe game history-யின் ஒவ்வொரு move-க்கும், button <button> கொண்ட list item <li> ஒன்றை உருவாக்குகிறீர்கள். அந்த button-க்கு jumpTo என்ற function-ஐ call செய்யும் onClick handler உள்ளது (அதை இன்னும் implement செய்யவில்லை).
இப்போது game-இல் நடந்த move-களின் பட்டியலையும் developer tools console-இல் ஒரு error-யையும் பார்க்க வேண்டும். “key” error என்ன அர்த்தம் என்பதைக் பேசுவோம்.
key ஒன்றைத் தேர்வு செய்தல்
list ஒன்றை render செய்யும்போது, render செய்யப்பட்ட ஒவ்வொரு list item பற்றிய சில தகவல்களை React சேமிக்கிறது. list-ஐ update செய்யும்போது, என்ன மாறியுள்ளது என்பதை React தீர்மானிக்க வேண்டும். நீங்கள் list item-களைச் சேர்த்திருக்கலாம், நீக்கியிருக்கலாம், re-arrange செய்திருக்கலாம், அல்லது update செய்திருக்கலாம்.
இதிலிருந்து transition ஆகிறது என்று நினைத்துப் பாருங்கள்:
<li>Alexa: 7 task-கள் மீதம்</li>
<li>Ben: 5 task-கள் மீதம்</li>இதற்கு:
<li>Ben: 9 task-கள் மீதம்</li>
<li>Claudia: 8 task-கள் மீதம்</li>
<li>Alexa: 5 task-கள் மீதம்</li>updated count-களுக்கு கூடுதலாக, இதைப் படிக்கும் மனிதர் நீங்கள் Alexa மற்றும் Ben-ன் ordering-ஐ swap செய்து, Alexa மற்றும் Ben இடையே Claudia-வை insert செய்தீர்கள் என்று கூறலாம். ஆனால் React ஒரு computer program; நீங்கள் நினைத்தது என்ன என்று அதற்கு தெரியாது. எனவே ஒவ்வொரு list item-ஐ அதன் sibling-களிலிருந்து வேறுபடுத்த ஒவ்வொரு list item-க்கும் key property குறிப்பிட வேண்டும். உங்கள் data database-இலிருந்து வந்திருந்தால், Alexa, Ben, மற்றும் Claudia-வின் database ID-களை key-களாகப் பயன்படுத்தலாம்.
<li key={user.id}>
{user.name}: {user.taskCount} task-கள் மீதம்
</li>list re-render செய்யப்படும் போது, React ஒவ்வொரு list item-ன் key-யையும் எடுத்து, matching key-க்காக முந்தைய list-ன் item-களை தேடுகிறது. தற்போதைய list-இல் முன்பு இல்லாத key இருந்தால், React component ஒன்றை உருவாக்குகிறது. முந்தைய list-இல் இருந்த key தற்போதைய list-இல் இல்லாவிட்டால், React முந்தைய component-ஐ destroy செய்கிறது. இரண்டு key-கள் match ஆனால், தொடர்புடைய component நகர்த்தப்படுகிறது.
Key-கள் ஒவ்வொரு component-ன் identity பற்றி React-க்கு தெரிவிக்கின்றன; இதனால் re-render-களுக்கிடையே state-ஐ React பராமரிக்க முடியும். component-ன் key மாறினால், component destroy செய்யப்பட்டு புதிய state உடன் மீண்டும் உருவாக்கப்படும்.
key என்பது React-இல் சிறப்பு மற்றும் reserved property. element உருவாக்கப்படும் போது, React key property-யை extract செய்து, returned element-இல் நேரடியாக key-யைச் சேமிக்கிறது. key props ஆக pass செய்யப்படுவது போல தோன்றினாலும், எந்த component-களை update செய்ய வேண்டும் என்பதைத் தீர்மானிக்க React தானாக key-ஐப் பயன்படுத்துகிறது. parent குறிப்பிட்ட key என்ன என்று ஒரு component கேட்க வழியில்லை.
dynamic list-களை உருவாக்கும் போதெல்லாம் சரியான key-களை assign செய்வது வலுவாக பரிந்துரைக்கப்படுகிறது. பொருத்தமான key இல்லையெனில், அது கிடைக்குமாறு உங்கள் data-வை restructure செய்வதை கருதலாம்.
key குறிப்பிடப்படாவிட்டால், React error report செய்து, default ஆக array index-ஐ key ஆகப் பயன்படுத்தும். list item-களை re-order செய்யும்போது அல்லது list item-களை insert/remove செய்யும்போது array index-ஐ key ஆகப் பயன்படுத்துவது பிரச்சினை தரும். key={i}-ஐ வெளிப்படையாக pass செய்வது error-ஐ அமைதிப்படுத்தும்; ஆனால் array index-களுக்கே உள்ள அதே பிரச்சினைகள் இதற்கும் உண்டு, பெரும்பாலான சூழல்களில் இது பரிந்துரைக்கப்படாது.
Key-கள் global ஆக unique ஆக வேண்டியதில்லை; component-களுக்கும் அவற்றின் sibling-களுக்கும் இடையே unique இருந்தால் போதும்.
time travel-ஐ implement செய்தல்
tic-tac-toe game history-யில், ஒவ்வொரு முந்தைய move-க்கும் அதனுடன் தொடர்புடைய unique ID உள்ளது: அது move-ன் sequential number. Move-கள் ஒருபோதும் re-order, delete, அல்லது நடுவில் insert செய்யப்படாது, எனவே move index-ஐ key ஆகப் பயன்படுத்துவது safe.
Game function-இல், <li key={move}> என key-ஐச் சேர்க்கலாம்; rendered game-ஐ reload செய்தால், React-ன் “key” error மறைய வேண்டும்:
const moves = history.map((squares, move) => {
//...
return (
<li key={move}>
<button onClick={() => jumpTo(move)}>{description}</button>
</li>
);
});import { useState } from 'react'; function Square({ value, onSquareClick }) { return ( <button className="square" onClick={onSquareClick}> {value} </button> ); } function Board({ xIsNext, squares, onPlay }) { function handleClick(i) { if (calculateWinner(squares) || squares[i]) { return; } const nextSquares = squares.slice(); if (xIsNext) { nextSquares[i] = 'X'; } else { nextSquares[i] = 'O'; } onPlay(nextSquares); } const winner = calculateWinner(squares); let status; if (winner) { status = 'வெற்றி பெற்றவர்: ' + winner; } else { status = 'அடுத்த வீரர்: ' + (xIsNext ? 'X' : 'O'); } return ( <> <div className="status">{status}</div> <div className="board-row"> <Square value={squares[0]} onSquareClick={() => handleClick(0)} /> <Square value={squares[1]} onSquareClick={() => handleClick(1)} /> <Square value={squares[2]} onSquareClick={() => handleClick(2)} /> </div> <div className="board-row"> <Square value={squares[3]} onSquareClick={() => handleClick(3)} /> <Square value={squares[4]} onSquareClick={() => handleClick(4)} /> <Square value={squares[5]} onSquareClick={() => handleClick(5)} /> </div> <div className="board-row"> <Square value={squares[6]} onSquareClick={() => handleClick(6)} /> <Square value={squares[7]} onSquareClick={() => handleClick(7)} /> <Square value={squares[8]} onSquareClick={() => handleClick(8)} /> </div> </> ); } export default function Game() { const [xIsNext, setXIsNext] = useState(true); const [history, setHistory] = useState([Array(9).fill(null)]); const currentSquares = history[history.length - 1]; function handlePlay(nextSquares) { setHistory([...history, nextSquares]); setXIsNext(!xIsNext); } function jumpTo(nextMove) { // TODO } const moves = history.map((squares, move) => { let description; if (move > 0) { description = 'நகர்வு #' + move + '-க்கு செல்'; } else { description = 'game தொடக்கத்துக்கு செல்'; } return ( <li key={move}> <button onClick={() => jumpTo(move)}>{description}</button> </li> ); }); return ( <div className="game"> <div className="game-board"> <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} /> </div> <div className="game-info"> <ol>{moves}</ol> </div> </div> ); } 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; }
jumpTo-ஐ implement செய்ய முன், பயனர் தற்போது எந்த step-ஐப் பார்க்கிறார் என்பதை Game component track செய்ய வேண்டும். இதைச் செய்ய, default 0 உடன் currentMove என்ற புதிய state variable-ஐ define செய்யுங்கள்:
export default function Game() {
const [xIsNext, setXIsNext] = useState(true);
const [history, setHistory] = useState([Array(9).fill(null)]);
const [currentMove, setCurrentMove] = useState(0);
const currentSquares = history[history.length - 1];
//...
}அடுத்து, அந்த currentMove-ஐ update செய்ய Game-க்குள் உள்ள jumpTo function-ஐ update செய்யுங்கள். currentMove மாற்றப்படும் number even ஆக இருந்தால், xIsNext-ஐ true ஆக அமைப்பீர்கள்.
export default function Game() {
// ...
function jumpTo(nextMove) {
setCurrentMove(nextMove);
setXIsNext(nextMove % 2 === 0);
}
//...
}square ஒன்றை click செய்யும்போது call செய்யப்படும் Game-ன் handlePlay function-இல் இப்போது இரண்டு மாற்றங்களைச் செய்வீர்கள்.
- நீங்கள் “காலத்தில் பின்சென்று” அந்த புள்ளியிலிருந்து புதிய move செய்தால், அந்த புள்ளிவரை உள்ள history-யை மட்டுமே வைத்திருக்க விரும்புகிறீர்கள்.
history-யில் உள்ள எல்லா item-களுக்குப் பிறகு (...spread syntax)nextSquares-ஐச் சேர்ப்பதற்கு பதிலாக,history.slice(0, currentMove + 1)-இல் உள்ள எல்லா item-களுக்குப் பிறகு அதைச் சேர்ப்பீர்கள்; இதனால் பழைய history-யின் அந்த பகுதியை மட்டுமே வைத்திருப்பீர்கள். - ஒவ்வொரு move செய்யப்பட்ட போதும், latest history entry-ஐச் சுட்டுமாறு
currentMove-ஐ update செய்ய வேண்டும்.
function handlePlay(nextSquares) {
const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
setHistory(nextHistory);
setCurrentMove(nextHistory.length - 1);
setXIsNext(!xIsNext);
}இறுதியாக, எப்போதும் final move-ஐ render செய்வதற்கு பதிலாக, தற்போது தேர்ந்தெடுக்கப்பட்ட move-ஐ render செய்ய Game component-ஐ மாற்றுவீர்கள்:
export default function Game() {
const [xIsNext, setXIsNext] = useState(true);
const [history, setHistory] = useState([Array(9).fill(null)]);
const [currentMove, setCurrentMove] = useState(0);
const currentSquares = history[currentMove];
// ...
}game history-யில் எந்த step-ஐயாவது click செய்தால், அந்த step நடந்த பிறகு board எப்படி இருந்தது என்பதை காட்ட tic-tac-toe board உடனடியாக update ஆக வேண்டும்.
import { useState } from 'react'; function Square({value, onSquareClick}) { return ( <button className="square" onClick={onSquareClick}> {value} </button> ); } function Board({ xIsNext, squares, onPlay }) { function handleClick(i) { if (calculateWinner(squares) || squares[i]) { return; } const nextSquares = squares.slice(); if (xIsNext) { nextSquares[i] = 'X'; } else { nextSquares[i] = 'O'; } onPlay(nextSquares); } const winner = calculateWinner(squares); let status; if (winner) { status = 'வெற்றி பெற்றவர்: ' + winner; } else { status = 'அடுத்த வீரர்: ' + (xIsNext ? 'X' : 'O'); } return ( <> <div className="status">{status}</div> <div className="board-row"> <Square value={squares[0]} onSquareClick={() => handleClick(0)} /> <Square value={squares[1]} onSquareClick={() => handleClick(1)} /> <Square value={squares[2]} onSquareClick={() => handleClick(2)} /> </div> <div className="board-row"> <Square value={squares[3]} onSquareClick={() => handleClick(3)} /> <Square value={squares[4]} onSquareClick={() => handleClick(4)} /> <Square value={squares[5]} onSquareClick={() => handleClick(5)} /> </div> <div className="board-row"> <Square value={squares[6]} onSquareClick={() => handleClick(6)} /> <Square value={squares[7]} onSquareClick={() => handleClick(7)} /> <Square value={squares[8]} onSquareClick={() => handleClick(8)} /> </div> </> ); } export default function Game() { const [xIsNext, setXIsNext] = useState(true); const [history, setHistory] = useState([Array(9).fill(null)]); const [currentMove, setCurrentMove] = useState(0); const currentSquares = history[currentMove]; function handlePlay(nextSquares) { const nextHistory = [...history.slice(0, currentMove + 1), nextSquares]; setHistory(nextHistory); setCurrentMove(nextHistory.length - 1); setXIsNext(!xIsNext); } function jumpTo(nextMove) { setCurrentMove(nextMove); setXIsNext(nextMove % 2 === 0); } const moves = history.map((squares, move) => { let description; if (move > 0) { description = 'நகர்வு #' + move + '-க்கு செல்'; } else { description = 'game தொடக்கத்துக்கு செல்'; } return ( <li key={move}> <button onClick={() => jumpTo(move)}>{description}</button> </li> ); }); return ( <div className="game"> <div className="game-board"> <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} /> </div> <div className="game-info"> <ol>{moves}</ol> </div> </div> ); } 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; }
இறுதி cleanup
code-ஐ மிகவும் நெருக்கமாகப் பார்த்தால், currentMove even ஆக இருக்கும் போது xIsNext === true, மற்றும் currentMove odd ஆக இருக்கும் போது xIsNext === false என்பதை கவனிக்கலாம். வேறு வார்த்தைகளில், currentMove-ன் மதிப்பு தெரிந்தால், xIsNext என்ன இருக்க வேண்டும் என்பதை எப்போதும் கணக்கிடலாம்.
இரண்டையும் state-இல் சேமிக்க எந்த காரணமும் இல்லை. உண்மையில், redundant state-ஐ எப்போதும் தவிர்க்க முயலுங்கள். state-இல் சேமிப்பதை தெளிவுப்படுத்துவது bug-களை குறைத்து, code-ஐப் புரிந்துகொள்வதை உதவும். xIsNext-ஐ தனி state variable ஆக சேமிக்காமல், currentMove அடிப்படையில் கணக்கிடுமாறு Game-ஐ மாற்றுங்கள்:
export default function Game() {
const [history, setHistory] = useState([Array(9).fill(null)]);
const [currentMove, setCurrentMove] = useState(0);
const xIsNext = currentMove % 2 === 0;
const currentSquares = history[currentMove];
function handlePlay(nextSquares) {
const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
setHistory(nextHistory);
setCurrentMove(nextHistory.length - 1);
}
function jumpTo(nextMove) {
setCurrentMove(nextMove);
}
// ...
}xIsNext state declaration அல்லது setXIsNext call-கள் இனி தேவையில்லை. இப்போது component-களை code செய்யும்போது தவறு செய்தாலும், xIsNext currentMove-உடன் sync இழக்கும் வாய்ப்பு இல்லை.
முடிவுரை
வாழ்த்துகள்! நீங்கள் இத்தகைய tic-tac-toe game ஒன்றை உருவாக்கியுள்ளீர்கள்:
- tic-tac-toe விளையாட அனுமதிக்கிறது,
- ஒரு வீரர் game-ஐ வென்றதை காட்டுகிறது,
- game முன்னேறும் போது game history-யைச் சேமிக்கிறது,
- வீரர்கள் game history-யை review செய்து game board-ன் முந்தைய version-களைப் பார்க்க அனுமதிக்கிறது.
நல்ல வேலை! React எப்படி வேலை செய்கிறது என்பது பற்றி இப்போது உங்களுக்கு நல்ல பிடி கிடைத்திருக்கும் என நம்புகிறோம்.
இறுதி முடிவைப் இங்கே பாருங்கள்:
import { useState } from 'react'; function Square({ value, onSquareClick }) { return ( <button className="square" onClick={onSquareClick}> {value} </button> ); } function Board({ xIsNext, squares, onPlay }) { function handleClick(i) { if (calculateWinner(squares) || squares[i]) { return; } const nextSquares = squares.slice(); if (xIsNext) { nextSquares[i] = 'X'; } else { nextSquares[i] = 'O'; } onPlay(nextSquares); } const winner = calculateWinner(squares); let status; if (winner) { status = 'வெற்றி பெற்றவர்: ' + winner; } else { status = 'அடுத்த வீரர்: ' + (xIsNext ? 'X' : 'O'); } return ( <> <div className="status">{status}</div> <div className="board-row"> <Square value={squares[0]} onSquareClick={() => handleClick(0)} /> <Square value={squares[1]} onSquareClick={() => handleClick(1)} /> <Square value={squares[2]} onSquareClick={() => handleClick(2)} /> </div> <div className="board-row"> <Square value={squares[3]} onSquareClick={() => handleClick(3)} /> <Square value={squares[4]} onSquareClick={() => handleClick(4)} /> <Square value={squares[5]} onSquareClick={() => handleClick(5)} /> </div> <div className="board-row"> <Square value={squares[6]} onSquareClick={() => handleClick(6)} /> <Square value={squares[7]} onSquareClick={() => handleClick(7)} /> <Square value={squares[8]} onSquareClick={() => handleClick(8)} /> </div> </> ); } export default function Game() { const [history, setHistory] = useState([Array(9).fill(null)]); const [currentMove, setCurrentMove] = useState(0); const xIsNext = currentMove % 2 === 0; const currentSquares = history[currentMove]; function handlePlay(nextSquares) { const nextHistory = [...history.slice(0, currentMove + 1), nextSquares]; setHistory(nextHistory); setCurrentMove(nextHistory.length - 1); } function jumpTo(nextMove) { setCurrentMove(nextMove); } const moves = history.map((squares, move) => { let description; if (move > 0) { description = 'நகர்வு #' + move + '-க்கு செல்'; } else { description = 'game தொடக்கத்துக்கு செல்'; } return ( <li key={move}> <button onClick={() => jumpTo(move)}>{description}</button> </li> ); }); return ( <div className="game"> <div className="game-board"> <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} /> </div> <div className="game-info"> <ol>{moves}</ol> </div> </div> ); } 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; }
உங்களிடம் கூடுதல் நேரம் இருந்தாலோ, புதிய React திறன்களைப் பயிற்சி செய்ய விரும்பினாலோ, tic-tac-toe game-க்கு செய்யக்கூடிய சில மேம்பாட்டு யோசனைகள் இங்கே; சிரமம் அதிகரிக்கும் வரிசையில் பட்டியலிடப்பட்டுள்ளன:
- தற்போதைய move-க்கு மட்டும், button-க்கு பதிலாக “நீங்கள் நகர்வு #… இல் உள்ளீர்கள்” என்று காட்டுங்கள்.
- square-களை hardcode செய்வதற்கு பதிலாக இரண்டு loop-களைப் பயன்படுத்த
Board-ஐ rewrite செய்யுங்கள். - move-களை ascending அல்லது descending order-இல் sort செய்ய அனுமதிக்கும் toggle button ஒன்றைச் சேருங்கள்.
- யாராவது வென்றால், வெற்றிக்கு காரணமான மூன்று square-களை highlight செய்யுங்கள் (யாரும் வெல்லாவிட்டால், முடிவு draw என்று message காட்டுங்கள்).
- move history list-இல் ஒவ்வொரு move-க்கான location-ஐ (row, col) format-இல் காட்டுங்கள்.
இந்த tutorial முழுவதும், elements, components, props, மற்றும் state உட்பட பல React concept-களைத் தொட்டுள்ளீர்கள். game உருவாக்கும்போது இந்த concept-கள் எப்படி வேலை செய்கின்றன என்பதை பார்த்துள்ளீர்கள்; app-ன் UI உருவாக்கும்போது இதே React concept-கள் எப்படி வேலை செய்கின்றன என்பதைப் பார்க்க React-இல் சிந்தித்தல்-ஐப் பாருங்கள்.