We want to hear from you!Take our 2021 Community Survey!
This site is no longer updated.Go to react.dev

Öğretici: React'e giriş

These docs are old and won’t be updated. Go to react.dev for the new React docs.

The updated Tutorial teaches modern React and includes live examples.

Bu öğretici, herhangi bir React bilginizin olmadığını varsayar.

Öğreticiye Başlamadan Önce

Bu öğreticide küçük bir oyun geliştireceğiz. Oyun geliştiricisi olmadığınızdan dolayı bu öğreticiyi atlamak istiyor olabilirsiniz — ama bir şans vermeniz iyi olacaktır. Zira bu öğreticide edineceğiniz teknikler herhangi bir React uygulaması geliştirmek için temel niteliğindedir, ve bu temeller üzerinde uzmanlaşmak React’i daha derinlemesine öğrenmenizi sağlayacaktır.

Not:

Bu öğretici, kodlayarak öğrenmek isteyen kişiler için tasarlanmıştır. Eğer bu konseptleri her yönüyle edinmek isterseniz adım adım öğrenme rehberini inceleyebilirsiniz. İncelediğinizde, öğreticinin ve adım adım öğrenme rehberinin birbirini tamamlayıcı nitelikte olduğunu görebilirsiniz.

Bu öğretici birkaç bölüme ayrılmıştır:

Bu öğreticiden yararlanmanız için tüm bölümleri tamamen bitirmek zorunda değilsiniz. Bir-iki bölüm tamamlamanız bile sizin için yararlı olacaktır. Fakat yine de tüm bölümleri tamamlamaya çalışınız.

Bu öğreticiyi takip ederken kodları kopyala-yapıştır yaparak denemenizde bizce hiçbir sorun yoktur. Fakat elle kodlayarak ilerlemenizi tavsiye ederiz. Bu sayede kas hafızanız gelişecek ve React’i daha güçlü bir şekilde öğrenmiş hale geleceksiniz.

Ne kodlayacağız?

Bu öğreticide, React ile bir tic-tac-toe (XOX oyunu) nasıl geliştirilir onu göstereceğiz.

Buradan oyunun son halini görebilirsiniz. Eğer bu kodlara aşina değilseniz ve size karışık geliyorsa endişelenmeyin. Çünkü bu öğreticinin amacı, React’in ve React’teki kod yapısının anlaşılmasında size yardımcı olmaktır.

Bu öğreticiye başlamadan önce, yukarıda belirttiğimiz linke giderek oyunu oynamanızı ve incelemenizi tavsiye ediyoruz. Oyunu oynadığınızda farkedeceğiniz gibi; oyun tahtasının sağında, numaralandırılmış bir liste bulunmaktadır. Bu liste size, oyunda oluşan hamlelerin bir geçmişini sunar ve oyunda ilerledikçe liste de otomatik olarak güncellenir.

Oyunu inceledikten sonra ilgili sayfayı kapatabilirsiniz. Çünkü bu öğreticiye sıfırdan bir şablonla başlayacağız.

Gelin şimdi oyunu kodlamak için gereken geliştirme ortamının kurulumuna değinelim.

Ön gereksinimler

Bu öğreticide, HTML ve JavaScript’i az-çok bildiğinizi varsayacağız. Fakat herhangi bir programlama dilinden gelseniz bile aşamaları takip edebilirsiniz. Ayrıca temel programlama konseptleri olan fonksiyonlara, nesnelere, dizilere ve az da olsa sınıflara aşina olduğunuzu varsayıyoruz.

Eğer JavaScript hakkında bilgi edinmeniz gerekiyorsa, bu rehberi okumanızı tavsiye ederiz. JavaScript’in en güncel versiyonu olan ES6 (EcmaScript 6)‘dan bazı özellikleri kullanacağız. Bu öğreticide de hepsi birer ES6 özelliği olan arrow function, class, let, ve const ifadelerini kullanıyor olacağız. ES6, henüz her tarayıcı tarafından tam olarak desteklenmediği için Babel REPL‘ı kullanarak ES6 kodunun derlenmiş halini görebilirsiniz.

Öğretici İçin Kurulum

Bu öğreticiyi tamamlamanın iki yolu bulunmaktadır: kodu tarayıcınızda yazabilir veya bilgisayarınıza yerel geliştirme ortamını kurabilirsiniz.

Kurulum Seçeneği 1: Kodu Tarayıcıda Yazma

Başlamanız için en kolay olan yöntemdir.

Öncelikle, buradaki başlangıç kodunu yeni sekmede açınız. Yeni sekme, React kodu ile birlikte boş bir tic-tac-toe oyununu görüntülüyor olacaktır. Bu öğreticide React kodunu düzenliyor olacağız.

Bu seçeneği tercih ediyorsanız, ikinci seçeneği es geçebilir, ve Genel bakış bölümüne giderek genel bilgi edinebilirsiniz.

Kurulum Seçeneği 2: Yerel Geliştirme Ortamı

Bu seçenek tamamen isteğe bağlıdır ve bu öğreticiyi takip etmek için zorunlu değildir.


İsteğe bağlı: Projeyi yerel ortamınızda geliştirmek için tıklayınız.

Bu kurulum seçeneği, üzerinde daha fazla çalışmayı gerektirir. Fakat aynı zamanda favori metin editörünüzü kullanarak projeyi geliştirmenize de olanak sağlar. İzlenecek adımlar aşağıdaki gibidir:

  1. Node.js‘in güncel versiyonunun yüklü olduğundan emin olunuz.
  2. Yeni bir proje oluşturmak için Create React App uygulaması kurulum yönergelerini takip ediniz. Aşağıdaki komutla my-app adında yeni bir React uygulaması oluşturunuz:
npx create-react-app my-app
  1. Yeni projenin src/ dizininin içerisindeki tüm dosyaları siliniz:

Uyarı:

src dizininin kendisini silmeyiniz, sadece içerisinde yer alan ve varsayılan olarak gelen kaynak dosyalarını siliniz. Çünkü 4. adımda, yeni React uygulaması ile gelen varsayılan dosyaları, bu projenin dosyaları ile değiştireceğiz.

# Aşağıdaki komutlar yardımıyla projenin src dizinine gidiniz:
cd my-app
cd src

# Daha sonra rm veya del kullanarak src dizininin içerisindeki dosyaları siliniz.

# Eğer Mac veya Linux kullanıyorsanız rm komutunu kullanabilirsiniz:
rm -f *

# Windows kullanıyorsanız del komutunu kullanabilirsiniz:
del *

# Proje dizinine geri dönünüz:
cd ..
  1. src/ dizininde index.css dosyasını oluşturup buradaki CSS kodunu ekleyiniz.
  2. src/ dizininde index.js dosyasını oluşturup buradaki JS kodunu ekleyiniz.
  3. src/ dizinindeki index.js dosyasını açıp, en üste aşağıdaki import satırlarını ekleyiniz:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';

Artık projeyi çalıştırabilirsiniz. Konsolda proje dizinindeyken npm start komutunu girdiğinizde, tarayıcıda http://localhost:3000 url’i açılacak ve devamında boş bir tic-tac-toe oyunu görüyor olacaksınız.

Metin editörünüzde, kodun renkli halde görüntülenmesini sağlamak için buradaki yönergeleri izlemenizi tavsiye ederiz.

Takıldım, Yardım Edin!

Eğer bu öğreticiyi takip ederken herhangi bir yerde takıldıysanız, topluluk destek kaynaklarına bakınız. Özellikle Discord’da yer alan Reactiflux Chat kanalı, hızlıca yardım almak için oldukça elverişlidir. Eğer bir cevap alamadıysanız veya hala takıldığınız için devam edemiyorsanız lütfen bize GitHub üzerinden issue açınız. Devamında size yardımıcı olacağız.

Genel Bakış

Kurulumu tamamladığınıza göre artık, React’e giriş yapabiliriz.

React Nedir?

React, kullanıcı arayüzleri oluşturmak için açık, verimli ve esnek bir JavaScript kütüphanesidir. Component (bileşen) denilen küçük ve izole parçalar sayesinde karmaşık arayüz birimlerini oluşturmanıza olanak tanır.

React’te birkaç tipte bileşen bulunmaktadır. Fakat şimdilik React.Component‘e değinelim:

class ShoppingList extends React.Component {
  render() {
    return (
      <div className="shopping-list">
        <h1>Shopping List for {this.props.name}</h1>
        <ul>
          <li>Instagram</li>
          <li>WhatsApp</li>
          <li>Oculus</li>
        </ul>
      </div>
    );
  }
}

// Örnek kullanım: <ShoppingList name="Mark" />

Birazdan yukarıda kullandığımız XML-tarzı etiketlere değineceğiz. React bileşenleri sayesinde, ekranda görmek istediğimiz arayüz birimlerini React’e belirtmiş oluyoruz. Verilerimiz değiştiği zaman React, etkili bir şekilde bileşenlerimizi güncelleyecek ve tekrar render edecektir (arayüze işleyecektir).

Buradaki ShoppingList, React bileşen sınıfıdır, veya React bileşen tipidir diyebiliriz. Bir React bileşeni, özellikler anlamına gelen “properties“‘in kısaltması olan props isimli parametreleri alır, ve arayüzü görüntülemek amacıyla render metodundan geriye bir görünüm hiyerarşisi (XML kodu) döndürür.

render metodu, ekranda neyi görüntülemek istiyorsanız onunla ilgili bir tanımlama geri döndürür. React de bu tanımlamayı alır ve görüntüler. Bilhassa render metodu, neyi render edeceği ile ilgili bir tanımlama olan React elemanı (React element) geri döndürür. Birçok React geliştiricisi, bu tanımlamaları kolayca kodlamak için “JSX” denilen özel bir kod dili kullanır. <div /> içeriği bir JSX kodu teşkil eder. Bu kod derlendiğinde, React.createElement('div') şeklinde bir JavaScript metot çağrısına dönüştürülür. Üstteki örneğin derlenmiş hali aşağıdaki gibidir:

return React.createElement('div', {className: 'shopping-list'},
  React.createElement('h1', /* ... h1 içeriği ... */),
  React.createElement('ul', /* ... ul içeriği ... */)
);

Kodun tam hali için tıklayınız.

Eğer createElement() fonksiyonu hakkında daha fazla bilgi almak istiyorsanız API dokümanını inceleyebilirsiniz. Fakat bu öğreticide createElement() fonksiyonunu kullanmayacağız. Bunun yerine daha basit ve okunaklı olan JSX gösterimini ele alacağız.

JSX, JavaScript’in bütün gücünü kullanacak şekilde tasarlanmıştır. Bu sayede, JSX kodu içerisinde süslü parantezler kullanarak herhangi bir JavaScript kodunu çalıştırabilirsiniz. Her React elemanı bir JavaScript nesnesi olduğu için, herhangi bir değişkene atayabilir veya uygulama içerisinde herhangi bir yere koyabilirsiniz.

Yukarıdaki ShoppingList bileşeni, yalnızca <div /> ve <li /> gibi HTML bileşenlerini render eder. Fakat uygulamanıza özel React bileşenleri oluşturarak, toplu halde render edilmesini sağlayabilirsiniz. Örneğin sadece <ShoppingList /> yazarak bütün alışveriş listesinin görüntülenmesini sağlayabilirsiniz. Her React bileşeni birbirinden izole edilmiştir ve birbirinden bağımsız olarak çalışabilir. Bu sayede basit bileşenleri bir araya getirerek karmaşık kullanıcı arayüzleri oluşturabilirsiniz.

Başlangıç Kodunun İncelenmesi

Eğer bu öğreticiyi tarayıcınızda takip ediyorsanız, başlangıç kodunu yeni sekmede açınız. Eğer öğreticiyi yerel makinenizde takip ediyorsanız, bunun yerine proje dizininde yer alan src/index.js dosyasını açınız kurulum aşamasında bu dosyaya değinmiştik).

Bu başlangıç kodu, yapacağımız proje ile ilgili bir temel niteliğindedir. tic-tac-toe oyununu programlayarak React öğreniminize yoğunlaşabilmeniz için size CSS kodlarını hazır olarak sunduk. Bu nedenle öğretici boyunca CSS kodu yazmanız gerekli değildir.

Kodu incelediğinizde aşağıdaki 3 React bileşenini fark edeceksiniz:

  • Square (Kare)
  • Board (Tahta)
  • Game (Oyun)

Şu an Square bileşeni yalnızca bir adet <button> elemanını, Board ise 9 adet Square bileşenini render ediyor. Game bileşeni ise Board bileşenini ve daha sonra değiştireceğimiz kısa bir metni render ediyor. Henüz uygulama içerisinde etkileşimli bir bileşen yer almıyor.

Prop’lar Aracılığıyla Veri Aktarımı

Şimdi işe koyulalım ve Board bileşeninden Square bileşenimize bazı verileri göndermeyi deneyelim.

Öğretici üzerinde çalışırken ve kopyala / yapıştır yerine, kodu elle yazmanızı şiddetle öneriyoruz. Bu, kas hafızanızı ve daha güçlü bir kavrayış geliştirmenize yardımcı olacaktır.

Board bileşeninin renderSquare metodunda, value prop’unu Square bileşenine gönderecek şekilde kodu değiştirelim:

class Board extends React.Component {
  renderSquare(i) {
    return <Square value={i} />;  }
}

Square bileşeninin render metodunu, ilgili değeri göstermesi için {/* TODO */} kısmını {this.props.value} şekilde değiştirelim:

class Square extends React.Component {
  render() {
    return (
      <button className="square">
        {this.props.value}      </button>
    );
  }
}

Öncesi:

React Devtools

Sonrası: Eğer değişiklikleri doğru bir şekilde uyguladıysanız render işlemi bitiminde her kare içerisinde bir sayı görüyor olmalısınız.

React Devtools

Kodun bu kısma kadar olan son halini görüntülemek için tıklayınız

Tebrikler! Board bileşeninden Square bileşenine “prop ile veri geçirmeyi” başardınız. React uygulamalarında prop’ların üst bileşenden alt bileşene geçişi sayesinde veri akışının oluşması sağlanır.

Etkileşimli bir Bileşen Yapımı

Haydi şimdi Square bileşenine tıkladığımızda içini “X” ile dolduralım. Öncelikle, Square bileşeninin render() fonksiyonundan dönen button etiketini bu şekilde değiştirelim:

class Square extends React.Component {
  render() {
    return (
      <button className="square" onClick={function() { console.log('click'); }}>        {this.props.value}
      </button>
    );
  }
}

Şimdi herhangi bir kareye tıkladığımızda, tarayıcınızın devtools konsolunda bir alert mesajı görüntülenecektir.

Not:

Daha az kod yazmak ve this‘in kafa karıştırıcı kullanımından kaçınmak için, butona tıklanması gibi olay fonksiyonlarında, arrow function kullanacağız:

class Square extends React.Component {
 render() {
   return (
     <button className="square" onClick={() => console.log('click')}>       {this.props.value}
     </button>
   );
 }
}

Farkedeceğiniz üzere, onClick={() => console.log('click')} kısmında butonun onClick prop’una bir fonksiyon ataması gerçekleştiriyoruz. Bu fonksiyon sadece butona tıkladığımızda çalışıyor. Genellikle bir yazılımcı hatası olarak parantezli ok () => ifadesinin unutulması yerine direkt olarak onClick={console.log('click')} ifadesinin yazılması gerçekleşebiliyor. Bu durumda tıklama anında gerçekleşmesi istenen olay yanlış bir şekilde çalışarak, bileşen tekrar render edildiğinde gerçekleşmiş oluyor.

Sonraki adımda Square bileşeninin, tıklandığı durumu “hatırlamasını” ve “X” işareti ile doldurulmasını sağlayacağız. Bir şeyleri “hatırlamak” için bileşenler state (durum)‘u kullanırlar.

React bileşenleri, constructor (yapıcı) fonksiyonlarında this.state‘e atama yaparak bir state’e sahip olurlar. React bileşeni içerisinde tanımlanan this.state özelliğinin erişim belirleyicisi private olarak düşünülmelidir. Çünkü sadece o bileşene özeldir ve diğer bileşenler tarafından direkt olarak erişilemezler.

Şimdi Square’in mevcut değerini this.state içerisinde saklayalım ve Square’e tıklandığında değiştirelim. Bunun için öncelikle Square sınıfına bir constructor ekleyeceğiz ve içerisinde state’in başlangıç değerlerini oluşturacağız:

class Square extends React.Component {
  constructor(props) {    super(props);    this.state = {      value: null,    };  }
  render() {
    return (
      <button className="square" onClick={() => console.log('click')}>
        {this.props.value}
      </button>
    );
  }
}

Not:

JavaScript class’larında, alt sınıfın constructor’ını oluştururken her zaman super fonksiyonunu çağırmanız gerekmektedir. constructor metodu olan her bir React sınıf bileşeninde, constructor metodu super(props) çağrısı ile başlamalıdır.

Şimdi, Square’e tıklanıldığında, state’indeki value değerinin render metodunda görüntülenebilmesi için aşağıdaki adımları izleyelim:

  • <button> etiketi içerisinde yer alan this.props.value yerine this.state.value yazalım.
  • () => alert() event handler’ını () => this.setState({value: 'X'}) ile değiştirelim.
  • Okunabilirliği arttırmak için className ve onClick prop’larını ayrı satırlara alalım.

Bu değişikliklerden sonra Square’in render metodundan dönen <button> etiketi aşağıdaki gibi görüntülenecektir:

class Square extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: null,
    };
  }

  render() {
    return (
      <button
        className="square"        onClick={() => this.setState({value: 'X'})}      >
        {this.state.value}      </button>
    );
  }
}

Square’in render metodundaki onClick metodundan, this.setState‘in çağrılmasını sağladık. Bu sayede Square’deki <button> elemanına her tıklandığında React, Square bileşenini tekrar render edecektir. Güncelleme sonrasında Square’in this.state.value değerine 'X' ataması gerçekleşecektir, ve bu sayede oyun tahtasında ‘X”i göreceğiz. Herhangi bir Square bileşenine tıklandığı anda içerisinde ‘X’ görüntülenecektir.

Bir bileşenteki setState fonksiyonunu çağırdığınızda, React otomatik olarak içerisindeki alt bileşenleri de güncellemiş oluyor.

Kodun bu kısma kadar olan son halini görüntülemek için tıklayınız

Geliştirici Araçları

Chrome ve Firefox için React Devtools eklentisi sayesinde herhangi bir React bileşen ağacını, tarayıcınızın geliştirici araçları kısmından görüntüleyebilirsiniz.

React Devtools

React DevTools, React bileşenlerinizin state’ini ve prop’larını kontrol etmenize olanak tanır.

React DevTools kurulumundan sonra, sayfa içerisindeki herhangi bir elemana sağ tıklayarak çıkan menüde “İncele“‘yi seçerseniz, geliştirici araçlarını açabilir, ve devamında en sağda yer alan React sekmesinde (“⚛️ Components” and “⚛️ Profiler”) incelemelerinizi yürütebilirsiniz. Bileşen ağacını incelemek için “⚛️ Components” tabına bakın.

Eklentinin CodePen ile çalışabilmesi için harici olarak birkaç adım daha vardır:

  1. CodePen’e e-posta adresiniz ile giriş yapınız veya kayıt olunuz (spam’lerin engellenmesi için gereklidir).
  2. “Fork” butonuna basınız.
  3. “Change View“‘a tıklayarak devamında “Debug mode“‘u seçiniz.
  4. Açılan yeni sekmede, Devtools içerisinde React sekmesi yer alacaktır.

Oyunun Tamamlanması

Artık tic-tac-toe oyunumuz için temel kod bloklarına sahibiz. Oyunun tamamlanması için tahta üzerinde “X” ve “O“‘ların birbiri ardına yerleştirilmesi gerekiyor. Sonrasında oyunda bir kazananın belirlenmesi için değişiklikler yapılmasına ihtiyaç var.

State’in Yukarı Taşınması

Şu an her bir Square bileşeni oyunun state’ini değiştirebiliyor. Kazananı belirleyebilmemiz için, 9 square’in de değerine ihtiyacımız var.

Bunu gerçekleştirmek için Board’un, her bir Square’e, kendi state’inin ne olduğunu sorması gerektiğini düşünebiliriz. Bu yöntem her ne kadar React’te uygulanabilir olsa da, yapmanızı tavsiye etmiyoruz. Çünkü bu şekilde yazılan kod; anlaşılabilirlikten uzak olacak, hataların oluşmasına daha müsait hale gelecek ve kodu refactor etmek istediğimizde bize çok daha büyük zorluklar çıkaracaktır. Bu nedenle, her bir Square sınıfında, kendi state’inin tutulmasının yerine, üst bileşen olan Board bileşeninde oyunun tüm state’ini tutmak en iyi çözümdür. Bunun sonucunda Board bileşeni, her bir Square’e neyi göstermesi gerektiğini prop’lar aracılığıyla aktarır (daha önce de prop’lar aracılığıyla her bir Square’e bir sayı atamıştık).

Bu örnekteki gibi, birçok alt bileşenden verilerin toplanması veya iki çocuğun birbirleri arasında iletişim kurabilmesi için, üst bileşende paylaşımlı bir state oluşturmanız gerekmektedir. Üst bileşen, prop’lar aracılığıyla state’ini alt bileşenlere aktarabilir. Bu sayede alt bileşenler hem birbirleri arasında hem de üst bileşen ile senkronize hale gelirler.

React bileşenleri refactor edilirken, state’in yukarı taşınması çok yaygın bir durumdur. Şimdi bu fırsatı değerlendirelim ve işe koyulalım.

Board’a bir constructor ekleyelim ve Board’un başlangıç state’ine bir dizi atayarak içerisinde 9 adet null değerinin bulunmasını sağlayalım. 9 kareye, 9 adet null karşılık gelecektir:

class Board extends React.Component {
  constructor(props) {    super(props);    this.state = {      squares: Array(9).fill(null),    };  }
  renderSquare(i) {
    return <Square value={i} />;
  }

Daha sonra Board’u doldurdukça, this.state.squares dizisinin içeriği aşağıdaki gibi görünmeye başlayacaktır:

[
  'O', null, 'X',
  'X', 'X', 'O',
  'O', null, null,
]

Board’un renderSquare metodu aşağıdaki gibi görünüyor:

  renderSquare(i) {
    return <Square value={i} />;
  }

Projeye başladığımızda, 0’dan 8’e kadar olan sayıları her bir karede göstermek için, Board’daki value prop’unu alt bileşenlere aktarmıştık. Bir diğer önceki aşamada ise sayıların yerine mevcut Square bileşeninin kendi state’i tarafından belirlenen “X” işaretinin almasını sağlamıştık. İşte bu nedenle Square bileşeni, Board tarafından kendisine gönderilen value prop’unu göz ardı ediyor.

Şimdi prop aktarma mekanizmasını tekrar kullanacağız. Bunun için her bir Square’e kendi mevcut değerini ('X', 'O', or null) atamak için Board bileşeninde değişiklik yapalım. Board’un constructor’ında halihazırda tanımladığımız bir squares dizisi bulunuyor. Board’un renderSquare metodunu, bu diziden verileri alacak şekilde değiştirelim:

  renderSquare(i) {
    return <Square value={this.state.squares[i]} />;  }

Kodun bu kısma kadar olan son halini görüntülemek için tıklayınız

Artık her bir Square bileşeni value prop’unu alacak ve ‘X’, ‘O’ veya boş square’ler için null değerini edinecektir.

Şimdi Square’e tıklandığında ne olacağına karar vermemiz gerekiyor. Board bileşeni artık hangi Square’in doldurulacağına karar verebildiğine göre, Square’e tıklandığında Board bileşeninin state’inin güncellenmesini sağlamalıyız. State her bir bileşene private olduğundan dolayı Square üzerinden direkt olarak Board’un state’ini değiştiremeyiz.

Board’un state’inin gizliliğini korumak için, Board’dan Square’e bir fonksiyon aktarmamız gerekiyor. Square’e her tıklama anında bu fonksiyonun otomatik olarak çağrısı gerçekleşecektir. Şimdi Board’un renderSquare metodunu aşağıdaki şekilde değiştirelim:

  renderSquare(i) {
    return (
      <Square
        value={this.state.squares[i]}
        onClick={() => this.handleClick(i)}      />
    );
  }

Not

Kodun okunabilirliği için geri dönüş elemanını birçok satıra böldük ve parantezler ekledik. Bu sayede JavaScript, return‘den sonra otomatik olarak bir noktalı virgül eklemeyecek ve bundan dolayı kodun bozulması engellenmiş hale gelecektir.

Artık Board’dan Square’e, value ve onClick olmak üzere iki tane prop gönderiyoruz. Square’e tıklandığında ise prop olarak gelen onClick fonksiyonu çağrılmasına ihtiyacımız var. Bunun için Square’e aşağıdaki değişiklikleri uygulamamız gerekiyor:

  • Square’in render metodu içerisinde yer alan this.state.value yerine this.props.value yazınız.
  • Yine Square’in render metodundaki this.setState() yerine this.props.onClick() yazınız.
  • Square artık oyunun state’ini değiştirmeyeceği için, Square’in constructor metodunu siliniz.

Bu değişikliklerin ardından, Square bileşeni aşağıdaki gibi görüntülenecektir:

class Square extends React.Component {  render() {    return (
      <button
        className="square"
        onClick={() => this.props.onClick()}      >
        {this.props.value}      </button>
    );
  }
}

Artık Square’e tıklandığında, Board tarafından aktarılan onClick fonksiyonu çağrılacaktır. Bunun nasıl gerçekleştiğini açıklayalım:

  1. HTML’de varsayılan olan <button> bileşeninin onClick prop’u React’e, tıklama olayını oluşturmasını söyler.
  2. Butona tıklandığında React, Square’in render() metodunda tanımlanan onClick fonksiyonunu çalıştırır.
  3. Bu fonksiyon ise, this.props.onClick() çağrısını gerçekleştirir. Square’in onClick prop’u, Board tarafından kendisine aktarılmıştır.
  4. Board, Square’e onClick={() => this.handleClick(i)} kodunu aktardığı için, Square’e tıklandığında Board’un this.handleClick(i) metodu çağrılır.
  5. Şu an handleClick() metodunu oluşturmadığımız için kodumuz hata verecektir. Şimdi bir kareye tıklarsanız, “this.handleClick is not a function” gibi bir şey söyleyen kırmızı bir hata ekranı görmelisiniz.

Not

HTML’deki <button> elemanı varsayılan bileşen olarak geldiği için, onClick fonksiyonu, React için özel bir anlam ihtiva eder. Fakat Square gibi özel olarak yazılan bileşenlerde, prop isimlendirmesi size kalmıştır. Bu nedenle Square’in onClick prop’unu veya Board’un handleClick metodunu daha farklı şekillerde isimlendirebilirsiniz. Ancak React’teki isimlendirme kuralına uymak gereklidir. Bu kural şu şekildedir: olayları temsil eden prop’lar için on[Olay], olayları handle eden metodlar için ise handle[Olay] ifadeleri kullanılır.

Square’e tıkladığımızda, handleClick‘i tanımlamadığımız için hata aldığımızdan bahsetmiştik. Gelin şimdi Board sınıfına handleClick‘i ekleyelim:

class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(null),
    };
  }

  handleClick(i) {    const squares = this.state.squares.slice();    squares[i] = 'X';    this.setState({squares: squares});  }
  renderSquare(i) {
    return (
      <Square
        value={this.state.squares[i]}
        onClick={() => this.handleClick(i)}
      />
    );
  }

  render() {
    const status = 'Next player: X';

    return (
      <div>
        <div className="status">{status}</div>
        <div className="board-row">
          {this.renderSquare(0)}
          {this.renderSquare(1)}
          {this.renderSquare(2)}
        </div>
        <div className="board-row">
          {this.renderSquare(3)}
          {this.renderSquare(4)}
          {this.renderSquare(5)}
        </div>
        <div className="board-row">
          {this.renderSquare(6)}
          {this.renderSquare(7)}
          {this.renderSquare(8)}
        </div>
      </div>
    );
  }
}

Kodun bu kısma kadar olan son halini görüntülemek için tıklayınız

Bu değişikliklerden sonra, oyundaki karelere tıkladığımızda içeriğinin “X” ile doluyor olduğunu tekrar görebiliyoruz. Fakat her bir Square’in ayrı ayrı state’e sahip olması yerine, Board bileşeninde tek bir state barındırılmış hale geldi. Bu sayede Board’daki state değiştiğinde tüm Square bileşenleri otomatik olarak tekrar render edilecektir. Bunun yanında, bütün Square’lerin state’inin Board bileşeninde tutulması, gelecekte kazananı belirlememiz için önemli bir yöntem teşkil edecektir.

Square bileşenleri artık state’i direkt olarak değiştirmediği için, değerleri Board bileşeninden alıyorlar ve tıklandıklarında Board’u haberdar ediyorlar. React terminolojisinde Square bileşenleri için controlled components (kontrol edilen bileşenler) adı verilir. Çünkü tüm kontrol Board bileşeninin elindedir.

Fark edeceğiniz gibi, handleClick fonksiyonu içerisinde, halihazırda var olan squares dizisini direkt olarak değiştirmek yerine, .slice()‘ı kullanarak bir kopyasını oluşturduk ve bu kopyayı değiştirdik. Şimdi squares dizisinin neden bir kopyasını oluşturduğumuza değineceğiz.

Neden Immutability Önemlidir

Immutability, anlam olarak mutate (değişmek) kelimesinin zıttı olan değişmezlik kavramını oluşturmaktadır. Önceki kod örneğinde, mevcut squares dizisini değiştirmek yerine, dizinin .slice() metodu ile bir kopyasının oluşturulması gerektiğini önermiştik. Şimdi ise immutability kavramına ve immutability’i öğrenmenin neden önemli olduğuna değineceğiz.

Genellikle verinin değiştirilmesi için iki farklı yaklaşım vardır. İlk yaklaşımda, verinin değerleri direkt olarak değiştirilerek ilgili verinin değişmesi (mutate) sağlanır. İkinci yaklaşımda ise, ilgili veri kopyalanarak, kopya veri üzerinde istenen değişiklikler gerçekleştirildikten sonra, kopya verinin ana veriye atanması işlemidir.

Mutasyon Kullanılarak Verinin Değiştirilmesi

var player = {score: 1, name: 'Jeff'};
player.score = 2;
// Oyuncu nesnesinin son hali: {score: 2, name: 'Jeff'}

Mutasyon Kullanılmadan Verinin Değiştirilmesi

var player = {score: 1, name: 'Jeff'};

var newPlayer = Object.assign({}, player, {score: 2});
// Şu an oyuncu nesnesi değişmedi, fakat oyuncu nesnesinden yeniOyuncu nesnesi oluşturuldu: {score: 2, name: 'Jeff'}

// Object spread sözdizimini kullanarak aşağıdaki gibi de yazabilirsiniz:
// var yeniOyuncu = {...player, score: 2};

Sonuç iki durumda da aynı oldu ama direkt olarak veriyi değiştirmeden kopya üzerinde değişiklikler yapmanın aşağıdaki gibi birçok yararı vardır.

Karmaşık Özellikleri Basit Hale Getirir

Immutability sayesinde karmaşık özellikleri kodlamak çok daha kolaydır. Bu öğreticinin sonunda, tic-tac-toe oyunundaki hamlelerin geçmişini incelemeyi ve önceki hamlelere geri dönmeyi sağlayan “zaman yolculuğu” özelliğini kodlayacağız. Bu özellik sadece oyunlarda değil, birçok uygulamada ileri ve geri alma işlemlerinin kurgulanması için bir gereksinim teşkil edebilir. Direkt olarak veri mutasyonundan kaçınarak, oyunun önceki versiyonlarını oyun geçmişinde bozmadan tutabilir ve daha sonra, önceki bir versiyona geri dönmeyi sağlayabilirsiniz.

Değişikliklerin Tespit Edilmesini Kolaylaştırır

Mutable nesneler, direkt olarak değiştirilebildikleri için, değişip/değişmediklerinin tespit edilmesi güçtür. Değişikliğin tespit edilebilmesi için, nesnenin kendisi ile önceki kopyalarının karşılaştırılması ve bütün nesne ağacı üzerinde gezilmesi gereklidir.

Immutable nesnelerdeki değişikliklerin tespit edilmesi daha kolaydır. Immutable nesne kopyalanarak ataması yapıldığı için, ilgili değişken, öncekinden farklı bir değişkene referans edilmişse o halde nesne değişmiştir diyebiliriz.

Tekrar Render Etme Zamanını Belirlemek

React’te Immutability’nin ana faydası ise, pure component‘ler (saf/katıksız bileşenler) yapmayı kolaylaştırmasıdır. Immutable veriler, değişiklik yapıldığını kolayca tespit edebilirler. Bu sayede değişiklik olduğunda ilgili bileşenin tekrar render edilmesine yardımcı olurlar.

Performansın iyileştirmesi yazısında shouldComponentUpdate() fonksiyonunun ne olduğuna ve nasıl pure component‘leri oluşturabileceğiniz hakkında bilgi edinebilirsiniz.

Fonksiyon bileşenleri

Square bileşenini nasıl fonksiyon bileşeni haline getireceğimize değinelim.

React’te fonksiyon bileşenleri, sadece render metodunu içerirler. İçerisinde herhangi bir state bulundurmadıkları için daha kolay bir şekilde bileşen oluşturmayı sağlarlar. React.Component‘tan türetilen bir sınıf bileşeni oluşturmak yerine, sadece propları girdi olarak alan ve render edilecek kısımları döndüren bir fonksiyon bileşeni yazabiliriz. Fonksiyon bileşenleri kısa bir şekilde yazıldığı için, sizi sınıf bileşenlerine göre daha az yorar.

Square sınıfını aşağıdaki fonksiyon ile değiştirelim:

function Square(props) {
  return (
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  );
}

Dikkat edecek olursanız sınıf bileşeninde kullandığımız this.props ifadesi yerine sadece props‘u kullandık.

Kodun bu kısma kadar olan son halini görüntülemek için tıklayınız

Not

Square’i, fonksiyon bileşeni olarak değiştirdiğimiz için, uzun olan onClick={() => this.props.onClick()} kod parçasını, onClick={props.onClick} şeklinde yazarak daha kısa hale getirmiş olduk (her iki taraftaki parantezlerin de gittiğine dikkat ediniz). Sınıf bileşeninde gerçek this değerine ulaşmak için arrow (ok) fonksiyonu kullanmıştık. Bunun aksine fonksiyon bileşenlerinde this ile uğraşmanıza gerek yoktur.

Hamle Sırası Değişikliği

Şimdi tic-tac-toe oyunumuzdaki hatayı çözmemiz gerekiyor. Oyunun son hali ile sadece “X” eklenebiliyor ama “O” eklenemiyor.

Oyuna varsayılan olarak “X” başlıyor. X’in ilk başlayıp/başlamayacağını Board’un constructor’ındaki başlangıç state’inde belirleyebiliriz:

class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(null),
      xIsNext: true,    };
  }

Herhangi bir oyuncu hamlesini yaptığında xIsNext (xSonrakiElemanMı) boolean değişkeninin tersini alarak hangi oyuncunun sonraki hamleyi yapacağını belirleyebiliriz. Ayrıca oyunun state’inde bu değişkeni kaydedebiliriz. Board’un handleClick fonksiyonunu, xIsNext değişkeninin tersini alacak şekilde ilgili değişikliği yapalım:

  handleClick(i) {
    const squares = this.state.squares.slice();
    squares[i] = this.state.xIsNext ? 'X' : 'O';    this.setState({
      squares: squares,
      xIsNext: !this.state.xIsNext,    });
  }

Bu değişiklik ile sayesinde, “X“‘ler ve “O“‘lar sırasıyla hamle yapabiliyor olacaklar.

Ayrıca oyunda, sıradaki hamlenin kimde olduğunu gösteren metni değiştirmek için, Board’un render metodunda “status” değişkenini oluşturabiliriz:

  render() {
    const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
    return (
      // Kalan kısımlar değişmedi

Bu değişikliklerden sonra Board bileşeninin son hali aşağıdaki gibi olacaktır:

class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(null),
      xIsNext: true,    };
  }

  handleClick(i) {
    const squares = this.state.squares.slice();    squares[i] = this.state.xIsNext ? 'X' : 'O';    this.setState({      squares: squares,      xIsNext: !this.state.xIsNext,    });  }

  renderSquare(i) {
    return (
      <Square
        value={this.state.squares[i]}
        onClick={() => this.handleClick(i)}
      />
    );
  }

  render() {
    const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
    return (
      <div>
        <div className="status">{status}</div>
        <div className="board-row">
          {this.renderSquare(0)}
          {this.renderSquare(1)}
          {this.renderSquare(2)}
        </div>
        <div className="board-row">
          {this.renderSquare(3)}
          {this.renderSquare(4)}
          {this.renderSquare(5)}
        </div>
        <div className="board-row">
          {this.renderSquare(6)}
          {this.renderSquare(7)}
          {this.renderSquare(8)}
        </div>
      </div>
    );
  }
}

Kodun bu kısma kadar olan son halini görüntülemek için tıklayınız

Kazananın Belirlenmesi

Artık sonraki oyuncuyu görüntüleyebiliyoruz. Bundan sonraki amacımız olarak, oyunun bitmesi durumunu belirtmek için, oyunun kazanıldığını ve artık başka bir hamle kalmadığını göstermemiz gerekiyor. Bunun için, kazanan oyuncuyu belirtmek amacıyla, dosyanın sonuna yardımcı bir fonksiyon ekleyebiliriz:

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;
}

9 kareden oluşan bir dizi göz önüne alındığında, bu fonksiyon kazananı kontrol edecek ve uygun şekilde 'X', 'O' veya null döndürecektir.

Board’un render fonksiyonunda, calculateWinner(squares) fonksiyonunu çağırarak, ilgili oyuncunun kazanma durumunun kontrol edilmesini sağlayabiliriz. Hamleyi yapan oyuncu kazandıysa, “Winner: X” veya “Winner: O” gibi kazananı belirten bir metin görüntüleyebiliriz. Şimdi, Board’un render fonksiyonunda yer alan status değişkenini aşağıdaki şekilde değiştirelim:

  render() {
    const winner = calculateWinner(this.state.squares);    let status;    if (winner) {      status = 'Winner: ' + winner;    } else {      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');    }
    return (
      // geriye kalan kısımlar değiştirilmedi

Oyunda farkettiyseniz bir oyuncu, diğer oyuncunun işaretlediği karenin üstüne tekrar işaretleme yapabiliyor. Buna ek olarak oyun kazanıldığı durumda da tekrar işaretleme yapmayı engellemeliyiz. Bunun için Board’un handleClick fonksiyonunu, belirli koşullarda return edecek şekilde değiştirelim:

  handleClick(i) {
    const squares = this.state.squares.slice();
    if (calculateWinner(squares) || squares[i]) {      return;    }    squares[i] = this.state.xIsNext ? 'X' : 'O';
    this.setState({
      squares: squares,
      xIsNext: !this.state.xIsNext,
    });
  }

Kodun bu kısma kadar olan son halini görüntülemek için tıklayınız

Tebrikler. Artık çalışan bir tic-tac-toe oyununuz var. Ayrıca bu kısma kadar React’in temel özelliklerini de öğrenmiş durumdasınız. Bu nedenle aslında gerçek kazanan sizsiniz.

Zamanda Yolculuğun Eklenmesi

Son çalışma olarak, oyunda önceki hamlelere gitmeyi sağayacak olan “zamanda geriye gitme” özelliğini ekleyelim.

Hamlelerin Geçmişinin Saklanması

Eğer squares dizisine direkt olarak elle müdahale ederek değiştirseydik, zaman yolculuğu özelliğini geliştirmemiz daha zor olurdu.

Ancak, slice() fonksiyonu yardımıyla her hamleden sonra squares dizisinin kopyasını alarak immutable olarak değiştirilmesini sağladık. Bu durum bize, squares dizisinin geçmişteki her halinin kaydedebilmemize, ve halihazırda oluşan hamleler arasında gezinebilmemize imkan sağlamış oldu.

squares dizisinin geçmiş hallerini tutabilmek için history adında başka bir dizi oluşturabiliriz. history dizisi, oyunda ilk hamleden son hamleye kadar tahtanın tüm durumlarını barındırıyor olacaktır:

history = [
  // İlk hamleden öncesi
  {
    squares: [
      null, null, null,
      null, null, null,
      null, null, null,
    ]
  },
  // İlk hamleden sonrası
  {
    squares: [
      null, null, null,
      null, 'X', null,
      null, null, null,
    ]
  },
  // İkinci hamleden sonrası
  {
    squares: