Öğretici: Tic-Tac-Toe
Bu öğretici ile küçük bir tic-tac-toe oyunu yapacaksınız. Bu öğretici herhangi bir React bilgisi gerektirmez. Bu öğreticide öğreneceğiniz teknikler herhangi bir React uygulaması yapmak için gerekli olan temel tekniklerdir ve bunları tamamıyla anlamak React hakkında derinlemesine bir anlayış sağlayacaktır.
Bu öğretici birçok bölüme ayrılmıştır:
- Öğretici için kurulum bölümü size öğreticiyi takip etmeniz için bir başlangıç noktası verecektir.
- Genel bakış bölümü React’in temellerini öğretecektir: bileşenler, prop’lar ve state.
- Oyunu tamamlama bölümü React ile geliştirme yaparken sık kullanılan teknikleri öğretecektir.
- Zaman yolculuğu ekleme bölümü React’in eşsiz gücüne daha derin bir bakış getirecektir.
Ne yapıyorsunuz?
Bu öğreticide, React ile interektif bir tic-tac-toe oyunu yapacaksınız.
İşimiz bittiği zaman nasıl bir şeyin ortaya çıkacağını burada görebilirsiniz:
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 = 'Kazanan: ' + winner; } else { status = 'Sıradaki oyuncu: ' + (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 = '# numaralı harekete git' + move; } else { description = 'Oyunun başlangıcına git'; } 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; }
Eğer bu kod size şu an anlamlı gelmiyorsa ya da kodun sözdizimine yabancıysanız, merak etmeyin! Bu öğreticinin amacı React’i ve sözdizimini anlamanıza yardımcı olmaktır.
Öğreticiye devam etmeden önce yukarıdaki tic-tac-toe oyununu kontrol etmenizi tavsiye ederiz. Fark edeceğiniz özelliklerden biri de oyun tahtasının sağında numaralandırılmış bir listenin bulunmasıdır. Bu liste size oyun boyunca yapılmış tüm hamleleri gösterir ve oyun devam ettiği sürece bu liste güncellenir.
Tamamlanmış oyunla biraz vakit geçirdikten sonra devam edin. Bu öğreticide daha basit bir şablonla başlayacaksınız. Bir sonraki adımımız oyunu yapmaya başlamanız için sizi hazırlamaktır.
Öğretici için kurulum
Aşağıdaki canlı kod editöründe, sağ üst köşedeki Fork butonuna basın ve CodeSanbox websitesini kullanarak editörü yeni bir sayfada açın. CodeSandbox, tarayıcınızda kod yazmanıza ve kullanıcılarınızın yaptığınız uygulamayı nasıl göreceğini önizlemenize olanak sağlar. Açılan yeni sayfa boş bir kare ve bu öğretici için gerekli olan başlangıç kodunu gösterecektir.
export default function Square() { return <button className="square">X</button>; }
Genel bakış
Kurulumu tamamladığımıza göre, React’e genel bir bakış atalım!
Başlangıç kodunu inceleme
CodeSanbox’ta üç ana bölüm göreceksiniz:
- Files bölümünde
App.js
,index.js
,styles.css
gibi dosyaların listesi vepublic
dosyası vardır - Seçtiğiniz dosyanın kaynak kodunu göreceğiniz kod editörü bölümü
- Yazdığınız kodun nasıl görüntüleneceğini gösteren tarayıcı bölümü
Files bölümünde App.js
dosyası seçili olmalıdır. O dosyanın içeriği kod editöründe şöyle olmalıdır:
export default function Square() {
return <button className="square">X</button>;
}
Tarayıcı bölümü, içinde X olan bir kare göstermelidir:
Şimdi başlangıç kodundaki dosyalara bir göz atalım.
App.js
App.js
içindeki kod bir bileşen yaratır. React’te bileşen, bir kullanıcı arayüzü parçasını temsil eden tekrar kullanılabilir bir kod parçasıdır. Bileşenler, uygulamanızdaki UI elementleri render etmek, yönetmek ve güncellemek için kullanılır. Hadi şu bileşene satır satır bakarak ne olduğunu anlamaya çalışalım:
export default function Square() {
return <button className="square">X</button>;
}
İlk satır Square
adında bir fonksiyon tanımlar. export
JavaScript kelimesi bu fonksiyonun bu dosya dışındaki yerlerde de kullanılabilmesi sağlar. default
kelimesi ise bu fonksiyonu kullanan diğer dosyalara bu fonksiyonun dosyadaki ana fonksiyon olduğunu söyler.
export default function Square() {
return <button className="square">X</button>;
}
İkinci satır bir buton döndürür. return
JavaScript kelimesi bundan sonra gelen her şeyin, fonksiyonu çağırana bir değer olarak döndürüleceği anlamına gelir. <button>
bir JSX elementidir. JSX elemanı, neyi görüntülemek istediğinizi açıklayan JavaScript kodu ve HTML elemanlarının birleşimidir. className="square"
ise bir buton özelliği ya da CSS’e butonu nasıl stillendireceğini söyleyen prop’tur. X
buton içindeki metindir ve </button>
ifadesi JSX elementini kapatarak bundan sonra gelen içeriğin buton içine konmaması gerektiğini belirtir.
styles.css
CodeSandbox’ın Files bölümündeki styles.css
isimli dosyaya tıklayın. Bu dosya React uygulamanızın stillerini tanımlar. İlk iki CSS seçici (*
ve body
) uygulamanızın büyük bölümlerinini stilini tanımlarken, .square
seçicisi className
özelliğini .square
olduğu her bileşenin stilini tanımlar. Kodunuzda bu, App.js
dosyasındaki Square bileşeninizdeki düğmeyle eşleşir.
index.js
CodeSandbox’ın Files bölümündeki index.js
isimli dosyaya tıklayın. Öğretici sırasında bu dosyayı düzenlemeyeceksiniz ama bu dosya App.js
dosyası içerisinde oluşturduğunuz bileşen ile web tarayıcısı arasındaki köprüdür.
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './styles.css';
import App from './App';
1-5 satırları gerekli tüm parçaları bir araya getirir:
- React
- React’in web tarayıcıları ile konuşması için kütüphane (React DOM)
- bileşenlerinizin stilleri
App.js
içerisinde oluşturduğunuz bileşen.
Sayfanın geri kalanı tüm parçaları bir araya getirir ve public
dosyasındaki index.html
dosyasına son ürünü enjekte eder.
Tahtayı oluşturmak
App.js
dosyasına geri gidelim. Öğreticinin geri kalanını bu sayfada geçireceğiz.
Şu anda tahta tek bir kareden oluşmakta ama bizim dokuz taneye ihtiyacımız var! Şu şekilde kopyala yapıştır yaparak iki kare yapmak isterseniz:
export default function Square() {
return <button className="square">X</button><button className="square">X</button>;
}
Şu hatayı alacaksınız:
<>...</>
mi kullanmak istediniz?React bileşenleri tek bir JSX elementi döndürmelidir ve birden fazla bitişik iki buton gibi JSX elementi döndürmemelidir. Bunu düzeltmek için Fragment (<>
ve </>
) kullanıp çoklu bitişik JSX elementlerini şu şekilde sarabilirsiniz:
export default function Square() {
return (
<>
<button className="square">X</button>
<button className="square">X</button>
</>
);
}
Şimdi şunu görmelisiniz:
Güzel! Şimdi yapmanız gereken bir kaç kere daha kopyala yapıştır yaparak dokuz kare elde etmek ve…
Olamaz! Kareler bizim istediğimiz tahtadaki gibi grid haline değil, tek bir satırda. Bunu düzeltmek için karelerinizi div
ler kullanarak satırlar halinde gruplamanız ve bazı CSS sınıfları eklemeniz gerekmektedir. Bunu yaparken aynı zamanda her karenin nerede görüntülendiğini bilmek için her kareye bir numara vereceksiniz.
App.js
dosyası içinide, Square
bileşenini şöyle gözükecek halde güncelleyin:
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
dosyası içinde tanımlanmış CSS, className
’i board-row
olan div’leri stiller. Bileşenleri stillenmiş div
’ler ile satır halinde grupladığınza göre, tic-tac-toe tahtamızı elde etmiş oluruz:
Ancak şimdi bir sorunumuz var. Square
adlı bileşeniniz, artık bir kare değil. Bu sorunu bileşenin ismini Board
olarak değiştirerek çözelim:
export default function Board() {
//...
}
Bu noktada kodunuz şöyle gözükmelidir:
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> </> ); }
Prop’lar aracılığıyla veri iletme
Şimdi, oyuncu kareye tıkladığında karenin değerini boştan “X“‘e değiştirmek isteyeceksiniz. Tahtayı şu ana kadar nasıl oluşturduğunuzu düşünürsek, kareyi dokuz kez güncelleyen kodu kopyalayıp yapıştırmanız gerekir (her kare için bir defa)! Kopyala yapıştır yapmak yerine, React’in bileşen yapısı, karışık ve yinelenen kodlardan kaçınmak için yeniden kullanılabilir bir bileşen oluşturmanıza olanak sağlar.
İlk olarak, ilk karenizi tanımlayan satırı (<button className="square">1</button>
) Board
bileşeninizden yeni bir Square
bileşenine kopyalayacaksınız:
function Square() {
return <button className="square">1</button>;
}
export default function Board() {
// ...
}
Daha sonra JSX sözdizimini kullanarak Board bileşeninin Square
bileşenini render etmesini sağlayacaksınız:
// ...
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>
</>
);
}
Tarayıcının div
’lerinin aksine, kendi bileşenleriniz olan Board
ve Square
büyük harfle başlamak zorundadır.
Şuna bir göz atalım:
Olamaz! Daha önce sahip olduğunuz numaralandırılmış kareleriniz artık yok. Şimdi her karenin içinde “1” yazmakta. Bunu düzeltmek için, her karenin sahip olması gerektiği değeri (Board
) üst bileşeninden (Square
) alt bileşenine prop’lar ile ileteceksiniz.
Square
bileşenini Board
bileşeninden ilettiğiniz value
değerini okuyacak şekilde güncelleyin:
function Square({ value }) {
return <button className="square">1</button>;
}
function Square({ value })
ifadesi Square bileşenine value
prop’unun iletilebileceği anlamına gelmektedir.
Şimdi her bir karenin içinde 1
yerine value
değerini göstermek isteyeceksiniz. Şu şekilde yapmaya çalışın:
function Square({ value }) {
return <button className="square">value</button>;
}
Eyvah, istediğiniz şey bu değildi:
Siz kelime olan “value” değil JavaScript değişkeni olan value
değerini render etmek istemiştiniz. JSX’ten “JavaScript’e kaçmak” için süslü parentezleri kullanmanız gereklidir. JSX içinde value
etrafına süslü parentezleri koyun:
function Square({ value }) {
return <button className="square">{value}</button>;
}
Şimdilik boş bir tahta görmelisiniz:
Bunun nedeni, henüz Board
bileşeninin render ettiği her Square
bileşenine value
prop’unu iletmemesidir. Bunu düzeltmek için, Board
bileşeni tarafından render edilen her Square
bileşenine value
prop’unu ileteceksiniz:
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>
</>
);
}
Şimdi tahtayı dolu bir şekilde görmelisiniz:
GÜncellenmiş kodunuz şöyle gözükmelidir:
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> </> ); }
İnteraktif bir bileşen yapma
Hadi tıklandığı zaman Square
bileşenini X
ile dolduralım. Square
içinde bir handleClick
fonksiyonu bildirelim. Daha sonra, Square
tarafından döndürülen buton JSX elementinin prop’larına onClick
’i ekleyin:
function Square({ value }) {
function handleClick() {
console.log('tıklandı!');
}
return (
<button
className="square"
onClick={handleClick}
>
{value}
</button>
);
}
Şimdi bir kareye tıklarsanız CodeSandbox’taki Browser bölümünün altındaki Console sekmesinde "tıklandı!"
diyen bir log görmelisiniz. Kareye birden fazla defa tıklamak yeniden "tıklandı!"
log’layacaktır. Aynı mesajla tekrarlanan konsol log’ları, konsolda daha fazla satır oluşturmaz. Bunun yerine ilk "tıklandı!"
log’unun yanında artan bir sayaç göreceksiniz.
Bir sonraki adımda, Square bileşeninin tıklandığını “hatırlamasını” ve “X” işareti ile doldurulmasını isteyeceksiniz. Bir şeyleri “hatırlamak” için bileşenler state kullanır.
React, bileşeninizin bir şeyleri “hatırlamasını” sağlamak için bileşeninizden çağırabileceğiniz useState
adı verilen özel bir fonksiyon sağlar. Şimdi Square
’in şu anki değerini state’te saklayalım ve Square
tıklandığı zaman değiştirelim.
Dosyanın en üstüne useState
içe aktarın. Square
bileşeninden value
prop’unu kaldırın. Onun yerine, Square
bileşeninin başına useState
çağıran yeni bir satır ekleyin ve value
adında bir state değişkeni döndürmesini sağlayın:
import { useState } from 'react';
function Square() {
const [value, setValue] = useState(null);
function handleClick() {
//...
value
değeri saklar ve setValue
değeri değiştirmek için kullanılan bir fonksiyondur. useState
’e iletilen null
değeri bu state değişkeni için başlangıç değeri olarak kullanılır ve bu yüzden value
değeri null
olarak başlar.
Square
bileşeni artık prop almıyor, Board bileşeni tarafından render edilen dokuz Square bileşeninin tamamından value
prop’unu kaldıracaksınız:
// ...
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>
</>
);
}
Şimdi Square
bileşenini tıklandığında “X” gösterecek şekilde değiştirin. Olay yöneticisi içindeki console.log("clicked!");
ifadesini setValue('X');
ile değiştirin. Şimdi Square
bileşeniniz şöyle gözükecektir:
function Square() {
const [value, setValue] = useState(null);
function handleClick() {
setValue('X');
}
return (
<button
className="square"
onClick={handleClick}
>
{value}
</button>
);
}
onClick
yöneticisinden set
fonksiyonunu çağırarak, React’e, <button>
tıklandığında o Square
bileşenini yeniden render etmesini söylüyorsunuz. Güncellemeden sonra, Square
’nin value
değeri 'X'
olacaktır, böylelikle “X” yazısını oyun tahtasında görebileceksiniz. Herhangi bir Square’e tıklayın. Şimdi “X” gözükmelidir:
Her bir Square kendi state’ine sahiptir: her bir Square’de saklanan value
birbirlerinden tamamen bağımsızdır. Bileşende set
fonksiyonu çağırdığınız zaman, React, içindeki alt bileşenleri de otomatik olarak güncelleyecektir.
Yukarıdaki değişiklikleri yaptıktan sonra, kodunuz şöyle gözükecektir:
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 Geliştirici Araçları
React DevTools, React bileşenlerinizin prop’larını ve state’ini kontrol etmenize olanak sağlar. React DevTools sekmesini CodeSandbox’ın browser bölümünün en altında bulabilirsiniz:
Ekrandaki herhangi bir bileşeni incelemek için, React DevTools’un sol üst köşesindeki butonu kullanın:
Oyunu tamamlama
Bu noktada, tic-tac-toe oyununuz için temel yapı taşlarına sahipsiniz. Tam bir oyun için, tahtaya “X” ve “O“‘ları dönüşümlü olarak yerleştirmeniz ve kazananı belirlemeniz gerekiyor.
State’i yukarı kaldırma
Şu anda, her bir Square
bileşeni oyunun state’inin bir parçasını kontorl etmekte. Bir tic-tac-toe oyununda kazananı kontrol etmek için, Board
bileşeninin bir şekilde 9 Square
bileşeninin state’ini bilmesi gereklidir.
Bu duruma nasıl yaklaşırsınız? İlk olarak, Board
’ın her Square
’e o Square
’in state’ini “sorması” gerektiğini tahmin edebilirsiniz. Bu yaklaşım React’te teknik olarak mümkün olmasına rağmen, bu yaklaşımı kullanmanızı tavsiye etmiyoruz çünkü kod anlaması zor bir hale gelir, hatalara açık hale gelir ve sonradan düzenlenmesi zor bir hale gelir. Onun yerine, en iyi yaklaşım oyunun state’ini her bir Square
bileşeninde saklamak yerine üst eleman Board
bileşeninde saklamaktır. Board
bileşeni, her bir Square’e bir sayı ilettiğinizde yaptığınız gibi, bir prop ileterek her Square
’e ne göstereceğini söyleyebilir.
Birden fazla alt elemandan veri toplamak için ya da iki alt bileşenin birbiriyle iletişim kurabilmesini sağlamak için paylaşılan state’i bu bileşenlerin üst bileşeninde bildirin. Üst eleman state’ini alt bileşenlerine prop’lar aracılığı ile iletebilir. Böylelikle alt bileşenler birbirleriyle ve üst bileşenle senkronize olurlar.
React bileşenlerini sonradan düzenlenirken state’in üst bileşene kaldırılması sıkça yapılan bir durumdur.
Hadi bu fırsatı denemek için kullanalım. Board
bileşenini, square
adlı bir state’e sahip olacak ve state’i varsayılan olarak 9 kareye karşılık gelen 9 boş değerden oluşan bir diziye eşit olacak şekilde düzenleyelim:
// ...
export default function Board() {
const [squares, setSquares] = useState(Array(9).fill(null));
return (
// ...
);
}
Array(9).fill(null)
ifadesi dokuz elemanlı bir dizi oluşturur ve her elemanı null
yapar. Etrafındaki useState()
çağrısı, başlangıçta o diziye ayarlanan bir squares
state değişkeni bildirir. Dizideki her bir girdi bir karenin değerine denk gelir. Tahtayı doldurmaya başladığınız zaman, squares
dizisi şöyle gözükecektir:
['O', null, 'X', 'X', 'X', 'O', 'O', null, null]
Şimdi Board
bileşeninizin value
prop’unu render ettiği her bir Square
bileşenine iletmesi gereklidir:
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>
</>
);
}
Daha sonra, Square
bileşenini Board bileşeninden value
prop’unu alacak şekilde düzenleyeceksiniz. Bu, Square bileşeninin kendi value
state değerini ve butonun onClick
prop’unun kaldırılmasını gerektirecektir:
function Square({value}) {
return <button className="square">{value}</button>;
}
Bu noktada boş bir tic-tac-toe tahtası görüyor olmalısınız:
Ve kodunuz şu şekilde gözükmelidir:
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> </> ); }
Şimdi her bir Square 'X'
, 'O'
ya da boş kareler için null
değerine sahip value
prop’u alacaktır.
Sırada, Square
’e tıklandığı zaman ne olacağını değiştirmek vardır. Artık Board
bileşeni hangi karenin dolduruluğunu yönetmekle görevlidir. Square
’in Board
state’ini güncellemesi için bir yol bulmanız gerekecektir. State onu bildiren bileşene özel olduğu için, Board
’un state’ini direkt olarak Square
bileşeninden güncelleyemezsiniz.
Onun yerine, Board
bileşeninden Square
bileşenine bir fonksiyon ileteceksiniz ve kareye tıklandığı zaman Square
bileşeninin o fonksiyonu çağırmasını sağlayacaksınız. Square
bileşeninin tıklandığında çağıracağı fonksiyonla başlayacaksınız. Bu fonksiyonu onSquareClick
olarak adlandıracaksınız:
function Square({ value }) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
);
}
Sonra, onSquareClick
fonksiyonunu Square
bileşeninin prop’larına ekleyeceksiniz:
function Square({ value, onSquareClick }) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
);
}
Şimdi onSquareClick
prop’unu Board
bileşenindeki handleClick
adını vereceğiniz bir fonksiyona bağlayacaksınız. onSquareClick
’i handleClick
’e bağlamak için ilk Square
bileşeninin onSquareClick
prop’una bir fonksiyon ileteceksiniz:
export default function Board() {
const [squares, setSquares] = useState(Array(9).fill(null));
return (
<>
<div className="board-row">
<Square value={squares[0]} onSquareClick={handleClick} />
//...
);
}
Son olarak, Board’un state’ini tutan squares
dizisini güncellemek için Board bileşeninin içinde handleClick
fonksiyonunu tanımlayacaksınız:
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
fonksiyonu JavaScript slice()
dizi metodu ile squares
dizisinin bir kopyasını oluşturur (nextSquares
). Daha sonra, handleClick
fonksiyonu nextSquares
dizisini güncelleyerek ilk kareye ([0]
indeks) X
ekler.
setSquares
fonksiyonunu çağırmak React’e bileşenin state’inin değiştiğini söyler. Bu, squares
state’ini (Board
) kullanan bileşenlerin yanı sıra onun alt bileşenlerinin de (tahtayı oluşturan Square
bileşenleri) yeniden render edilmesini tetikleyecektir.
Artık tahtaya X’leri ekleyebilirsiniz… ama sadece solt üst kareye. handleClick
fonksiyonunuz sadece sol üst kareyi (0
) güncelleyecek şekilde kodlanmıştır. Hadi şimdi handleClick
fonksiyonunu herhangi bir kareyi güncelleyecek şekilde düzenleyelim. Güncellenecek karenin indeksini alacak şekilde handleClick
fonksiyonuna i
argümanını ekleyelim:
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 (
// ...
)
}
Daha sonra, bu i
argümanını handleClick
fonksiyonuna iletin. JSX’te karenin onSquareClick
prop’unu doğrudan handleClick(0)
olacak şekilde ayarlamayı deneyebilirsiniz ancak bu işe yaramayacaktır:
<Square value={squares[0]} onSquareClick={handleClick(0)} />
Şimdi bunun neden çalışmadığına bakalım. handleClick(0)
çağrısı Board bileşeninin render edilmesinde rol alacaktır çünkü handleClick(0)
Board bileşeninin state’ini setSquares
çağrısı ile değiştirir ve bütün Board bileşeniniz yeniden renderlanacaktır. Ancak bu handleClick(0)
çağrısını tekrar yapar ve sonsuz bir döngüye neden olur:
Peki bu sorun neden daha önce olmadı?
onSquareClick={handleClick}
ilettiğinizde, handleClick
fonksiyonunu prop olarak iletiyordunuz. Fonksiyonu çağırmıyordunuz! Ancak şimdi anında o fonksiyonu çağırıyorsunuz—handleClick(0)
etrafındaki parentezlere dikkat edin—ve fonksiyonun erken çalışmasının sebebi bu. handleClick
fonksiyonunu kullanıcı tıklayana kadar çağırmak istemezsiniz!
Bu sorunu çözmek için handleClick(0)
çağıran bir handleFirstSquareClick
, handleClick(1)
çağıran bir handleSecondSquareClick
fonksiyonu yaratabilirsiniz. Bu fonksiyonları onSquareClick={handleFirstSquareClick}
şeklinde prop olarak (çağırmak yerine) iletebilirsiniz. Böylelikle sonsuz döngü sorununu çözersiniz.
Ancak, dokuz farklı fonksiyon tanımlamak ve herbirine farklı isimler vermek çok fazla laf kalabalığıdır. Onun yerine, şöyle yapalım:
export default function Board() {
// ...
return (
<>
<div className="board-row">
<Square value={squares[0]} onSquareClick={() => handleClick(0)} />
// ...
);
}
Yeni () =>
sözdizimine dikkat edin. Burada, () => handleClick(0)
bir ok fonksiyonudur. Bu, fonksiyonları tanımlamanın kısa bir yoludur. Kareye tıklandığı zaman, =>
“oktan” sonraki kod çalışacaktır ve handleClick(0)
çağırılacaktır.
Şimdi ilettiğiniz ok fonksiyonlarından handleClick
’i çağırmak için diğer sekiz kareyi güncellemeniz gerekiyor. Her bir handleClick
çağrısına ilişkin argümanın doğru karenin indeksine karşılık geldiğinden emin olun:
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>
</>
);
};
Şimdi yine tahtadaki istediğiniz kareye tıklayarak X ekleyebilirsiniz:
Bu sefer tüm state’imiz Board
bileşeni tarafından yönetiliyor!
Kodunuz şu şekilde gözükmeli:
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> </> ); }
Artık state yönetiminiz Board
bileşeninde olduğundan, üst Board
bileşeni alt Square
bileşenine prop’ları ileterek onların doğru görüntülenmesini sağlar. Bir Square
’e tıklandığında, alt Square
bileşeni, üst Board
bileşeninden tahtanın state’inin güncellemesini ister. Board
’un state’i değiştiğinde, hem Board
bileşeni hem de her bir alt Square
bileşeni otomatik olarak yeniden render edilir. Tüm karelerin state’inin Board
bileşeninde tutulması ilerde kazananını belirlenmesinde yardımcı olacaktır.
Bir kullanıcı tahtadaki solt üst kareye bir X
eklemek için tıkladığında ne olacağını özetleyelim:
- Sol üstteki kareye tıklamak,
button
’ınSquare
’denonClick
prop’u olarak aldığı fonksiyonu çalıştırır.Square
bileşeni o fonksiyonuonSquareClick
prop’u olarakBoard
bileşeninden aldı.Board
bileşeni o fonksiyonu direkt olarak JSX içinde tanımladı.handleClick
fonksiyonunu0
argümanı ile çağıracaktır. handleClick
fonksiyonu (0
) argümanınısquare
dizisinin ilk elemanınınull
’danX
’e güncellemek için kullanır.Board
bileşenininsquares
state’i güncellendi, bundan dolayıBoard
bileşeni ve alt bileşenleri yeniden renderlandı. Bu da0
indeksliSquare
bileşenininvalue
prop’ununnull
’danX
’e değişmesine neden oldu.
Sonuçta, kullanıcı tıklamayı yaptıktan sonra sol üst karenin X
ile doldurulduğunu gördü.
Değişmezlik niye önemlidir
Mevcut diziyi değiştirmek yerine, squares
dizisinin bir kopyasını oluşturmak için handleClick
’te nasıl .slice()
çağırdığınıza dikkat edin. Nedenini açıklamak için, değişmezliği ve öğrenmenin neden önemli olduğunu tartışmamız gereklidir.
Veriyi değiştirmek için kullanılan genellikle iki yaklaşım vardır. İlk yaklaşım veriyi direkt olarak mutasyona uğratarak verinin değerlerini değiştirmektir. İkinci yaklaşım ise veriyi gerekli değişiklikler yapılmış kopyası ile değiştirmektir. Aşağıda eğer squares
dizisini mutasyona uğratsaydınız nasıl olacağını görebilirsiniz:
const squares = [null, null, null, null, null, null, null, null, null];
squares[0] = 'X';
// Şimdi `squares` dizisi: ["X", null, null, null, null, null, null, null, null];
Şimdi squares
dizisinin mutasyona uğratılmadan değiştirildiğinde nasıl gözükeceğine bakalım:
const squares = [null, null, null, null, null, null, null, null, null];
const nextSquares = ['X', null, null, null, null, null, null, null, null];
// `squares` dizisi aynı şekilde durmakta ama `nextSquares` dizisinin ilk elemanı `null` yerine `X` olmuş
Sonuç aynıdır ama direkt olarak mutasyona uğratmayarak (temel verileri değiştirmek) birkaç avantaj kazanırsınız.
Değişmezlik, karmaşık özelliklerin uygulanmasını çok daha kolay hale getirir. Bu öğreticinin ilerleyen bölümlerinde, oyunun geçmişini gözden geçirmenize ve geçmiş hamlelere “geri dönmenize” olanak tanıyan bir “zaman yolculuğu” özelliği uygulayacaksınız. Bu işlevsellik oyunlara özgü değildir—belirli eylemleri geri alma ve yeniden yapma yeteneği uygulamalar için yaygın bir gerekliliktir. Doğrudan veri mutasyonundan kaçınmak, verilerin önceki sürümlerini bozulmadan saklamanıza ve daha sonra yeniden kullanmanıza olanak tanır.
Değişmezliğin başka bir faydası daha vardır. Varsayılan olarak, bir üst bileşenin state’i değiştiğinde tüm alt bileşenler otomatik olarak yeniden render edilir. Bu, değişiklikten etkilenmeye alt bileşenler için bile geçerlidir. Her ne kadar yeniden render etme kullanıcı tarafından fark edilmese de (bundan kaçınmaya çalışmamalısınız!), performans nedenleriyle ağacın açıkça etkilenmeyen bir bölümünü yeniden render etmemek isteyebilirsiniz. Değişmezlik, bileşenlerin verilerinin değişip değişmediğinin karşılaştırılmasını çok ucuz bir hale getirir. React’in bir bileşenin ne zaman yeniden render edileceğini nasıl seçtiği hakkında daha fazla bilgiyi memo
API referansı sayfasında bulabilirsiniz.
Sırayla oynama
Şimdi bu tic-tac-toe oyunundaki büyük kusuru düzeltmenin zamanı geldi: “O“‘lar tahtaya işaretlenemiyor.
İlk hamleyi varsayılan olarak “X” olarak ayarlayacaksınız. Board bileşenine başka bir state parçası ekleyerek bunu takip edelim:
function Board() {
const [xIsNext, setXIsNext] = useState(true);
const [squares, setSquares] = useState(Array(9).fill(null));
// ...
}
Bir oyuncu her hamle yaptığında, xIsNext
(bir boolean) değeri çevrilerek sıradaki oyuncunun hangisi olacağı belirlenecek ve oyunun state’i kaydedilecektir. Board
’un handleClick
fonksiyonunu xIsNext
değerini çevirecek şekilde güncelleyeceksiniz:
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 (
//...
);
}
Şimdi, farklı karelere tıkladığınızda, olması gerektiği gibi X
ve O
arasında değişecekler!
Ama durun, bir sorun var. Aynı kareye birden çok kez tıklamayı deneyin:
X
’in üzerine bir O
yazılıyor! Bu oyuna çok ilginç bir değişiklik katacak olsa da, şimdilik orijinal kurallara bağlı kalacağız.
Bir kareyi X
veya O
ile işaretlediğinizde, önce karenin zaten bir X
veya O
değerine sahip olup olmadığını kontrol etmiyorsunuz. Bunu erken döndürerek düzeltebilirsiniz. Karenin zaten bir X
veya O
değerine sahip olup olmadığını kontrol edeceksiniz. Eğer kare zaten doluysa, handleClick
fonksiyonunda erken return
yapacaksınız - tahta state’ini güncellemeye çalışmadan önce.
function handleClick(i) {
if (squares[i]) {
return;
}
const nextSquares = squares.slice();
//...
}
Artık sadece boş karelere X
veya O
ekleyebilirsiniz! İşte bu noktada kodunuzun nasıl görünmesi gerektiği:
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> </> ); }
Kazanan belirleme
Artık oyuncular sırayla oynayabildiğine göre, oyunun ne zaman kazanıldığını ve yapılacak başka hamle kalmadığını göstermek isteyeceksiniz. Bunu yapmak için 9 kareden oluşan bir dizi alan, bir kazanan olup olmadığını kontrol eden ve uygun şekilde 'X'
, 'O'
veya null
döndüren calculataWinner
adlı yardımcı bir fonksiyon ekleyeceksiniz. calculateWinner
fonksiyonu hakkında çok fazla endişelenmeyin; React’e özgü bir fonksiyon değildir:
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;
}
Bir oyuncunun kazanıp kazanmadığını kontrol etmek için Board
bileşeninin handleClick
fonksiyonunda calculateWinner(squares)
fonksiyonunu çağıracaksınız. Bu kontrolü, kullanıcının zaten X
veya O
olan bir kareye tıklayıp tıklamadığını kontrol ederken aynı anda gerçekleştirebilirsiniz. Her iki durumda da erken döndürmek istiyoruz:
function handleClick(i) {
if (squares[i] || calculateWinner(squares)) {
return;
}
const nextSquares = squares.slice();
//...
}
Oyunculara oyunun ne zaman bittiğini bildirmek için “Kazanan” gibi bir metin görüntüleyebilirsiniz: “Kazanan: X” ya da “Kazanan: O”. Bunu yapmak için Board
bileşenine bir status
bölümü ekleyeceksiniz. Durum, oyun bittiyse kazananı gösterecek ve oyun devam ediyorsa sıranın hangi oyuncuda olduğunu gösterecektir:
export default function Board() {
// ...
const winner = calculateWinner(squares);
let status;
if (winner) {
status = "Kazanan: " + winner;
} else {
status = "Sıradaki oyuncu: " + (xIsNext ? "X" : "O");
}
return (
<>
<div className="status">{status}</div>
<div className="board-row">
// ...
)
}
Tebrikler! Artık çalışan bir tic-tac-toe oyununuz var. React’in temellerini de öğrenmiş oldunuz. Yani burada asıl kazanan sizsiniz. İşte kodunuz şöyle gözükmeli:
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 = 'Kazanan: ' + winner; } else { status = 'Sıradaki oyuncu: ' + (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; }
Zaman yolculuğu ekleme
Son bir egzersiz olarak, oyundaki önceki hamlelere “zamanda geri gitmeyi” mümkün kılalım.
Önceki hamleleri saklama
squares
dizisini mutasyona uğrattıysanız, zaman yolculuğunu uygulamak epey zor olacaktı.
Ancak, her hamleden sonra squares
dizisinin yeni bir kopyasını oluşturmak için slice()
metodunu kullandınız ve bunu değişmez olarak kabul ettiniz. Bu, squares
dizisinin geçmişteki her sürümünü saklamanıza ve daha önce gerçekleşmiş hamleler arasında gezinmenize olanak tanır.
Geçmiş squares
dizilerini, yeni bir state değişkeni olarak saklayacağınız history
adlı başka bir dizide saklayacaksınız. history
dizisi, ilk hamleden son hamleye kadar tüm tahta state’ini temsil eder ve aşağıdaki şekle sahiptir:
[
// İlk hamleden önce
[null, null, null, null, null, null, null, null, null],
// İlk hamleden sonra
[null, null, null, null, 'X', null, null, null, null],
// İkinci hamleden sonra
[null, null, null, null, 'X', null, null, null, 'O'],
// ...
]
Yeniden state’i yukarı kaldırma
Şimdi geçmiş hamlelerin bir listesini görüntülemek için Game
adında yeni bir üst-düzey bileşen yazacaksınız. Tüm oyun geçmişini içeren history
state’ini buraya yerleştireceksiniz.
history
state’ini Game
bileşenine yerleştirmek, squares
state’ini alt bileşeni olan Board
bileşeninden kaldırmanıza olanak sağlayacaktır. Tıpkı Square
bileşeninden Board
bileşenine “state’i kaldırdığınız” gibi, şimdi de Board
bileşeninden üst-düzey Game
bileşenine kaldıracaksınız. Bu, Game
bileşenine Board
’un verileri üzerinde tam kontrol sağlar ve Board
’a history
’den önceki hamleleri render etme talimatı vermesine olanak sağlar.
İlk olarak, export default
ile bir Game
bileşeni ekleyin. Bu bileşenin Board
bileşeni ve bazı işaretlemeleri render etmesini sağlayın:
function Board() {
// ...
}
export default function Game() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<ol>{/*YAPILACAKLAR*/}</ol>
</div>
</div>
);
}
export default
anahtar sözcüklerini function Board() {
bildiriminden önce kaldırdığınıza ve function Game() {
bildiriminden önce eklediğinize dikkat edin. Bu, index.js
dosyanıza Board
bileşeniniz yerine üst-düzey bileşen olarak Game
bileşenini kullanmanızı söyler. Game
bileşeni tarafından döndürülen ek div
’ler daha sonra tahtaya ekleyeceğiniz oyun bilgileri için yer açıyor.
Sırada hangi oyuncunun olduğunu ve hamle geçmişini izlemek için Game
bileşenine state’ler ekleyin:
export default function Game() {
const [xIsNext, setXIsNext] = useState(true);
const [history, setHistory] = useState([Array(9).fill(null)]);
// ...
Dikkat ederseniz [Array(9).fill(null)]
tek öğeli bir dizidir ve kendisi de 9 null
’dan oluşan bir dizidir.
Mevcut hamlenin karelerini render etmek için, history
dizisinden son kareler dizisini okumak isteyeceksiniz. Bunun için useState
’e ihtiyacınız yoktur—render sırasında bunu hesaplamak için zaten yeterli bilgiye sahipsiniz:
export default function Game() {
const [xIsNext, setXIsNext] = useState(true);
const [history, setHistory] = useState([Array(9).fill(null)]);
const currentSquares = history[history.length - 1];
// ...
Ardından, Game
bileşeni içinde, oyunu güncellemek için Board
bileşeni tarafından çağrılacak handlePlay
fonksiyonunu oluşturun. xIsNext
, currentSquares
ve handlePlay
’i Board
bileşenine prop olarak iletin:
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) {
// YAPILACAKLAR
}
return (
<div className="game">
<div className="game-board">
<Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
//...
)
}
Şimdi Board
bileşenini aldığı prop’lar tarafından tamamen kontrol edilebilir hale getirelim. Board
bileşenini üç prop alacak şekilde değiştirin: xIsNext
, squares
ve bir oyuncu hamle yaptığında Board
’un güncellenmiş squares
dizisiyle çağırabileceği yeni bir onPlay
fonksiyonu. Ardından, Board
fonksiyonunun useState
çağrısı yapan ilk iki satırı kaldırın:
function Board({ xIsNext, squares, onPlay }) {
function handleClick(i) {
//...
}
// ...
}
Şimdi Board
bileşenindeki handleClick
içindeki setSquares
ve setXIsNext
çağrılarını yeni onPlay
fonksiyonunuzda tek bir çağrı ile değiştirin, böylece Game
bileşeni kullanıcı bir kareye tıkladığında Board
’u güncelleyebilir:
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
bileşeni tamamen Game
bileşeni tarafından kendisine aktarılan prop’lar tarafından kontrol edilir. Oyunun tekrar çalışmasını sağlamak için Game
bileşeninde handlePlay
fonksiyonunu uygulamanız gerekir.
handlePlay
çağırıldığında ne yapmalıdır? Board’un eskiden setSquares
fonksiyonunu güncellenmiş bir dizi ile çağırdığını hatırlayın; şimdi güncellenmiş squares
dizisi onPlay
fonksiyonuna iletiliyor.
handlePlay
fonksiyonun yeniden render etmeyi tetiklemek için Game
state’ini güncellemesi gerekiyor ancak artık çağırabileceğiniz bir setSquares
fonksiyonu yok—artık bu bilgiyi saklamak için history
state değişkenini kullanıyorsunuz. Güncellenmiş squares
dizisini yeni bir geçmiş girişi olarak ekleyerek history
değişkenini güncellemek isteyeceksiniz. Ayrıca, Board’un eskiden yaptığı gibi xIsNext
’i de değiştirmek isteyeceksiniz:
export default function Game() {
//...
function handlePlay(nextSquares) {
setHistory([...history, nextSquares]);
setXIsNext(!xIsNext);
}
//...
}
Burada, [...history, nextSquares]
history
ve ardından nextSquares
içindeki tüm öğeleri içeren yeni bir dizi oluşturur. (...history
spread sözdizimi ifadesini “history
içindeki tüm öğeleri sırala” şeklinde okuyabilirsiniz.)
Örneğin, eğer history
[[null,null,null], ["X",null,null]]
ve nextSquares
["X",null,"O"]
ise, yeni [...history, nextSquares]
dizisi [[null,null,null], ["X",null,null], ["X",null,"O"]]
olacaktır.
Bu noktada, state’i Game
bileşeni içine taşıdınız ve kullanıcı arayüzü, tıpkı düzenleme işleminden önce olduğu gibi tamamen çalışıyor olmalıdır. İşte bu noktada kodun nasıl görünmesi gerektiği:
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 = 'Kazanan: ' + winner; } else { status = 'Sıradaki oyuncu: ' + (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>{/*YAPILACAKLAR*/}</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; }
Önceki hamleleri göstermek
Tic-tac-toe oyununun hamlelerini kaydettiğinize göre, oyunculara önceki hamlelerin bir listesini gösterebilirsiniz.
<button>
gibi React elemetleri sıradan JavaScript nesneleridir; bu elementleri uygulamanızda her yere iletebilirsiniz. React’te birden fazla öğe render etmek için, React elementlerinden oluşan bir dizi kullanabilirsiniz.
Halihazırda state’inizde geçmiş
hamlelerin bir dizisi var, yani artık bu diziyi React elementlerinden oluşan bir diziye dönüştürmelisiniz. JavaScript’te, bir diziyi başka bir diziye dönüştürmek için, dizi map
metodunu kullanabilirsiniz:
[1, 2, 3].map((x) => x * 2) // [2, 4, 6]
Hamle history
state’ini ekrandaki butonları temsil eden React elementlerine dönüştürmek ve geçmiş hamlelere “atlamak” için bir buton listesi görüntülemek için map
metodunu kullanacaksınız. Game bileşenindeki history
state’i üzerinde map
metodunu uygulayalım:
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) {
// YAPILACAKLAR
}
const moves = history.map((squares, move) => {
let description;
if (move > 0) {
description = '# numaralı hamleye git' + move;
} else {
description = 'Oyunun başlangıcına git';
}
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>
);
}
Aşağıda kodunuzun nasıl olması gerektiğini görebilirsiniz. Geliştirici araçları konsolunda şunu söyleyen bir hata görmeniz gerektiğine dikkat edin:
Bu hatayı bir sonraki bölümde düzelteceksiniz.
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 = 'Kazanan: ' + winner; } else { status = 'Sıradaki oyuncu: ' + (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) { // YAPILACAKLAR } const moves = history.map((squares, move) => { let description; if (move > 0) { description = '# numaralı hamleye git' + move; } else { description = 'Oyunun başlangıcına git'; } 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
fonksiyonuna ilettiğiniz history
dizisi içinde gezerken, squares
argümanı history
dizisinin her bir elemanından geçer ve move
argümanı her bir dizi indeksinden geçer: 0
, 1
, 2
, …. (Çoğu durumda, gerçek dizi elemanlarına ihtiyacınız olacaktır ancak bir hamle listesi oluşturmak için yalnızca indekslere ihtiyacınız olacaktır.)
Tic-tac-toe oyunundaki her bir hamle için bir buton <button>
içeren <li>
elementi oluşturacaksınız. Buton, jumpTo
(daha yazmadınız) adı verilen fonksiyonu onClick
yöneticisi ile çağıracaktır.
Şimdilik oyunda gerçekleşen hamlelerin bir listesini ve geliştirici araçları konsolunda bir hata görmelisiniz. “key” hatasının ne anlama geldiğini tartışalım.
Bir key (anahtar) seçme
Bir liste render ettiğinizde, React render edilen her bir liste öğesi hakkında bazı bilgileri saklar. Bir listeyi güncellediğinizde, React neyin değiştiğini saptamalıdır. Listenin öğelerine ekleme, çıkarma, yeniden düzenleme veya güncelleme yapmış olabilirsiniz.
Şundan
<li>Ahmet: 7 görevi kaldı</li>
<li>Ayşe: 5 görevi kaldı</li>
şuna değiştiğini düşünelim
<li>Ayşe: 9 görevi kaldı</li>
<li>Zeynep: 8 görevi kaldı</li>
<li>Ahmet: 5 görevi kaldı</li>
Güncellenen sayıları ek olarak, bunu okuyan bir insan muhtemelen Ahmet ve Ayşe’nin sıralamasını değiştirdiğinizi ve Zeynep’i Ahmet ve Ayşe arasına eklediğinizi söylecektir. Ancak, React bir bilgisayar programıdır ve neyi amaçladığınızı bilemez, bu nedenle her liste öğesini kardeş öğelerinden ayırmak için her bir liste öğesi için bir key özelliği belirtmeniz gerekir. Verileriniz bir veritabanından alınmış olsaydı, Ayşe, Zeynep ve Ahmet’in veritabanı kimlikleri anahtar olarak kullanılabilirdi.
<li key={user.id}>
{user.name}: {user.taskCount} görevi kaldı
</li>
Bir liste yeniden render edildiğinde, React her liste öğesinin anahtarını alır ve önceki listenin öğelerinde eşleşen bir anahtar arar. Geçerli listede daha önce olmayan bir anahtar varsa, React bir bileşen oluşturur. Geçerli listede önceki listede var olan bir anahtar eksikse, React önceki bileşeni yok eder. Eğer iki anahtar eşleşirse, ilgili bileşen taşınır.
Anahtarlar React’e her bileşenin kimliği hakkında bilgi verir, bu da React’in yeniden oluşturma işlemleri arasında durumu korumasını sağlar. Bir bileşenin anahtarı değişirse, bileşen yok edilir ve yeni bir state ile yeniden oluşturulur.
key
React’te özel ve ayrılmış bir özelliktir. Bir eleman oluşturulduğunda, React key
özelliğini çıkarır ve anahtarı doğrudan döndürülen elemanda saklar. Her ne kadar key
özelliği prop olarak iletilmiş gibi görünse de, React hangi bileşenlerin güncelleneceğine karar vermek için otomatik olarak key
özelliğini kullanır. Bir bileşenin, üst elemanının hangi key
i belirttiğini sormasının bir yolu yoktur.
Dinamik listeler oluşturduğunuzda uygun anahtarlar kullanmanız şiddetle tavsiye edilir. Uygun bir anahtarınız yoksa, verilerinizi uygun bir hale getirecek şekilde yeniden yapılandırmayı düşünebilirsiniz.
Herhangi bir anahtar belirtilmemişse, React bir hata bildirir ve dizi indeksini anahtar olarak kullanılır. Dizi indeksini anahtar olarak kullanmak, bir listenin öğelerini yeniden sıralamaya çalışırken veya liste öğelerini ekleyip/çıkarırken sorunludur. Açıkça key={i}
iletmek hatayı susturur ancak dizi indekslerini kullanmak ile aynı sorunlara sahiptir ve çoğu durumda önerilmez.
Anahtarların global olarak benzersiz olması gerekmez; yalnızca bileşenler ve kardeşleri arasında benzersiz olmaları gerekir.
Zaman yolculuğu uygulamak
Tic-tac-toe oyunun geçmişinde, her geçmiş hamlenin kendi benzersiz ID’si vardır: hamlenin sıralı numarasıdır. Hamleler asla yeniden sıralanmayacak, silinmeyecek ya da araya yenileri eklenmeyecektir bu nedenle hamle indeksini anahtar olarak kullanmak güvenlidir.
Game
fonksiyonunda, anahtarı <li key={move}>
olarak ekleyebilirsiniz ve render edilen oyunu yenilerseniz, React’in “key” hatası ortadan kalkacaktır:
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 = 'Kazanan: ' + winner; } else { status = 'Sıradaki oyuncu: ' + (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 = '# numaralı hamleye git' + move; } else { description = 'Oyunun başlangıcına git'; } 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
fonksiyonunu uygulamadan önce, kullanıcının o anda hangi hamleyi görünütlediğini takip etmek için Game
bileşenine ihtiyacınız vardır. Bunu yapmak için, varsayılan değeri 0
olan currentMove
adında yeni bir state değişkeni tanımlayın:
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];
//...
}
Daha sonra, Game
içindeki jumpTo
fonksiyonunu currentMove
’u güncelleyecek şekilde düzenleyin. Ayrıca, currentMove
değerini değiştirdiğiniz sayı çift ise xIsNext
değerini true
olarak ayarlayacaksınız.
export default function Game() {
// ...
function jumpTo(nextMove) {
setCurrentMove(nextMove);
setXIsNext(nextMove % 2 === 0);
}
//...
}
Şimdi bir kareye tıklandığında çağırılan Game
’in handlePlay
fonksiyonunda iki tane değişiklik yapacaksınız.
- Eğer “zamanda geriye gider” ve o noktadan itibaren yeni bir hamle yaparsanız, geçmişi sadece o noktaya kadar tutmak isterseniz.
nextSquares
öğelerinihistory
içindeki tüm öğelerden (...
spread sözdizimi) sonra eklemek yerine,history.slice(0, currentMove + 1)
içindeki tüm öğelerden sonra eklersiniz, böylece eski geçmişin yalnıza o kısmını saklarsınız. - Her hamle yapıldığında,
currentMove
değişkenini en son hamle girdisine işaret edecek şekilde güncellemeniz gerekir.
function handlePlay(nextSquares) {
const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
setHistory(nextHistory);
setCurrentMove(nextHistory.length - 1);
setXIsNext(!xIsNext);
}
Son olarak, Game
bileşenini her zaman son hamleyi render edecek şekilde değilde seçili hamleyi render edecek şekilde güncelleyeceksiniz:
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];
// ...
}
Oyunun geçmişindeki herhangi bir hamleye tıklarsanız, tic-tac-toe tahtası o adım gerçekleştikten sonra tahtanın nasıl göründüğünü göstermek için hemen güncellenmelidir.
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 = 'Kazanan: ' + winner; } else { status = 'Sıradaki oyuncu: ' + (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 = '# numaralı hamleye git' + move; } else { description = 'Oyunun başlangıcına git'; } 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; }
Son temizlik
Eğer kodu yakından incelerseniz, currentMove
çift ise xIsNext === true
, currentMove
tek ise xIsNext === false
olduğunu göreceksiniz. Diğer bir deyişle eğer currentMove
değişkeninin değerini biliyorsanız, xIsNext
değerinin ne olduğunu tahmin edebilirsiniz.
Bunların her ikisini de state’te saklamanız için hiçbir neden yoktur. Aslında, her zaman gereksiz state’ten kaçınmaya çalışın. State’te sakladıklarınızı basitleştirmek hataları azaltır ve kodunuzun anlaşılmasını kolaylaştırır. Game
bileşenini xIsNext
değişkenini ayrı bir state olarak saklamayacak ve bunun yerine currentMove
değişkenine göre hesaplayacak şekilde değiştirin:
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);
}
// ...
}
Artık xIsNext
state değişkenine ya da setXIsNext
çağrısına ihtiyacanız yoktur. Artık bileşenleri kodlarken bir hata yapsanız bile xIsNext
’in currentMove
ile senkronizasyonunun bozulma ihtimali yoktur.
Toparlama
Tebrikler! Şunları yapan bir tic-tac-toe oyunu yarattınız:
- Tic-tac-toe oynamanızı sağlayan,
- Bir oyuncunun oyunu ne zaman kazandığını gösteren,
- Oyun ilerledikçe geçmiş hamleleri saklayan,
- Oyuncuların geçmiş hamleleri incelemesine ve oyun tahtasının önceki durumlarını görmesine olanak tanıyan.
Güzel iş! Umarız React’in nasıl çalıştığı hakkında hatırı sayılır bir bilgiye sahip olmuşsunuzdur.
Kodun son haline göz atın:
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 = 'Kazanan: ' + winner; } else { status = 'Sıradaki oyuncu: ' + (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 = '# numaralı hamleye git' + move; } else { description = 'Oyunun başlangıcına git'; } 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; }
Eğer fazladan zamanınız varsa ya da yeni React becerilerinizi geliştirmek istiyorsanız, işte tic-tac-toe oyununda yapabileceğiniz iyileştirmeler için bazı fikirler artan zorluk seviyesine göre sıralanmıştır:
- Yalnızca geçerli hamle için, bir buton yerine ”# numaralı hamledesiniz…” ifadesini gösterin.
- Kareleri doğrudan koda yazmak yerine iki döngü kullancak şekilde
Board
bileşenini tekrar yazın. - Yapilan hamleleri artan veya azalan şekilde görüntülemenizi sağlayan bir buton ekleyin.
- Biri oyunu kazandığında, kazanmaya sebep olan kareleri başka bir renkle belirtin (kimse kazanmadığında, oyunun berabere bittiğini söyleyen bir mesaj gösterin).
- Her hamlenin konumunu hamle geçmişi listesinde (satır, sütun) biçiminde gösterin.
Bu öğretici boyunca elementler, bileşenler, prop’lar ve state gibi React kavramlarını öğrendiniz. Artık bu kavramların bir oyun yaparken nasıl çalıştığını gördüğünze göre, aynı React kavramlarının bir uygulamanın kullanıcı arayüzünü yaparken nasıl çalıştığını görmek için React’te Düşünmek sayfasına göz atın.