State ve Lifecycle

Önceki konularda gördüğümüz saat örneğini düşünün.

Şu ana kadar UI’ı güncellemenin tek bir yolunu öğrendik.

Görüntülenen çıktıyı değiştirmek için ReactDOM.render() fonksiyonunu her saniye çağırıyorduk:

function tick() {
  const element = (
    <div>
      <h1>Merhaba Dünya!</h1>
      <h2>Saat şu anda {new Date().toLocaleTimeString()}</h2>
    </div>
  );
  ReactDOM.render(
    element,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

CodePen’de Deneyin

Bu bölümde, Clock componentini yeniden kullanımının doğru yöntemini öğreneceğiz.

Zamanlayıcıyı kendisi ayarlayacak ve her saniyede bir güncelleme yapacaktır.

Clock componentinin nasıl göründüğünü yazmakla başlayabiliriz:

function Clock(props) {
  return (
    <div>
      <h1>Merhaba Dünya!</h1>
      <h2>Saat şu anda {props.date.toLocaleTimeString()}</h2>
    </div>
  );
}

function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

CodePen’de Deneyin

Daha önce anlatılmıştı fakat tekrar etmekte fayda var. Clock componenti çağrılırken attribute olarak date={new Date()} gönderiliyor. Bu attribute, Clock componentine parametre olarak gelir ve adı propstur. Clock componentine bu tarihi yazdırmak içinde props.date şeklinde kullanıyoruz.

Bunu bir kez yazmak ve Clock component güncellemesini kendisi gerçekleştirsin isteriz:

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

Bunu uygulamak için Clock componentine state eklemeliyiz. Stateler propslara benzer, ancak tamamen component tarafından kontrol edilir. Daha önce belirttiğimiz gibi, class olarak tanımlanan componentlerin bazı ilave özellikler var. State yalnızca classlar için kullanılabilen bir özelliktir.

Bir Fonksiyonun Class'a Dönüştürülmesi

Bu kısıma kadar componentleri fonksiyon olarak tanımlamıştık. Fakat React’ta class olarak oluşturmaya alışmak daha doğru olacaktır. Şimdi adım adım bir fonksiyonun class’a çevrilme işlemini inceleyelim.

Clock fonksiyon componentini aşağıdaki adımlarla class componentine dönüştürebiliriz.

  1. React.Componentine extend eden aynı ada sahip bir ES6 classı oluşturalım. (class Clock extends React.Component)

  2. Buna render() adı verilen boş bir fonksiyon ekleyin.

  3. Fonksiyonun içerisindeki kodları render()ın içerisine taşıyın.

  4. propsun adını this.props olarak değiştirin.

class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Merhaba Dünya!</h1>
        <h2>Saat şu anda {this.props.date.toLocaleTimeString()}</h2>
      </div>
    );
  }
}

CodePen’de Deneyin

Fonksiyon component yerine class component kullanımına alışın. Üst kısımdaki adımları anlamadıysanız tekrar tekrar okumanızda fayda var.

Clock componenti bir fonksiyon yerine bir class olarak tanımlanmış oldu.

Bu, state ve lifecycle (yaşam döngüsü) gibi ek özellikleri kullanmamızı sağlar.

Bir Class'a State Eklemek

Bir class’a state eklemek için aşağıdaki 3 adımı dikkatlice inceleyiniz.

  1. this.props.date satırını this.state.date olarak güncelleyin:
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Merhaba Dünya!</h1>
        <h2>Saat şu anda {this.state.date.toLocaleTimeString()}</h2>
      </div>
    );
  }
}
  1. Class componentimizin içerisine state’i ilk anda tanımlayan bir constructor oluşturalım.

Constructor fonksiyonu, bir classla beraber oluşturulan, nesnenin oluşturulması ve başlatılması için özel bir fonskiyondur. Bir classta “constructor” ismiyle yalnızca bir tane özel fonskiyon olabilir. Class oluşturulduğu anda içerisindeki kodlar çalışır.

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Merhaba Dünya!</h1>
        <h2>Saat şu anda {this.state.date.toLocaleTimeString()}</h2>
      </div>
    );
  }
}

propsun constructorden nasıl alındığına dikkat ediniz:

constructore parametre olarak props ekleyip, içerisine super(props); satırını ekliyoruz.

  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

Class componenleri her zaman constructore props ile çağırmalıdır. <Clock /> componentinden date attribute’ünü kaldırın:

ReactDOM.render(
  //eski hali = <Clock date={new Date()} />
  <Clock />,
  document.getElementById('root')
);

Zamanlayıcı kodunu daha sonra componentin kendisine geri ekleyeceğiz.

Şu anda sonuç şöyle görünüyor:

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Merhaba Dünya!</h1>
        <h2>Saat şu anda {this.state.date.toLocaleTimeString()}</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

CodePen’de Deneyin

Şimdi, Clocki kendi zamanlayıcısını oluşturup her saniyede bir güncelleyeceğiz.

Bir Classa Lifecycle Fonksiyonları Ekleme

Birçok componente sahip uygulamalarda, componentler yok edildiğinde alınan kaynakları boşaltmak çok önemlidir.

Clock, DOM’a ilk defa çağırıldığında bir zamanlayıcı ayarlamak istiyoruz. Buna React’te mounting denir.

Ayrıca, Saat componentinden üretilen DOM kaldırıldığında, bu zamanlayıcıyı temizlemek istiyoruz. Buna ise React’te unmounting denir.

Bir component DOM’a yazdırılıp kaldırıldığında bazı kod satırlarını çalıştırmak için class componente özel yöntemler ekleyebiliriz:

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    //Component oluşturulduğunda çalışacak fonksiyon
  }

  componentWillUnmount() {
    //Component kaldırıldığında çalışacak fonksiyon
  }

  render() {
    return (
      <div>
        <h1>Merhaba Dünya!</h1>
        <h2>Saat şu anda {this.state.date.toLocaleTimeString()}</h2>
      </div>
    );
  }
}

Bu fonksiyonlara lifecycle hooks (yaşam döngüsü kancaları) adı verilir.

Component çıktısı DOM’a aktarıldıktan sonra componentDidMount() fonksiyonu çalışır. Bu, zamanlayıcı ayarlamak için iyi bir yerdir:

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

() => this.tick() kullanımı ES6 ile gelen kısa fonksiyon kullanımıdır.

ES5‘teki kullanımı function() { return this.tick(); }</i>

Zamanlayıcı thisin üzerine nasıl kaydettiğimizi unutmayın.

Zamanlayıcıyı componentWillUnmount() fonksiyonunda temizleyeceğiz:

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

Son olarak, Clock componentini her saniyede bir çalışacağı tick() adında bir fonksiyon oluşturacağız.

Bu fonksiyon state’eki tarihi this.setState() kullanarak güncelleyecektir.

Daha önceki konularda propsların yalnızca okunabilir olduğunu ve güncellenmemesi gerektiğinin notunu düşmüştük. Bizler (this.setState() ile) state‘i değiştireceğiz, React ise o state’i kullanan yerleri (propsları) kendisi güncelleyecek.

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  //Fonksiyon tanımlarken başına "function" yazmadığımıza dikkat edin.
  //React componentleri içerisinde fonksiyon tanımlamak istediğimizde direkt isim vererek yazıyoruz.
  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Merhaba Dünya!</h1>
        <h2>Saat şu anda {this.state.date.toLocaleTimeString()}</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

CodePen’de Deneyin

Neler olup bittiğini hızla özetleyelim:

1) ReactDOM.render() içerisinde <Clock /> componenti çağırıldığında, React Clock componentinin constructorünü çağırır. Clock componentinin geçerli saati göstermesi gerektiği için this.state‘i geçerli saat ile başlatır. Daha sonra bu state güncellenecek.

2) React sonra Clock componentinin render() fonksiyonunu çağırır. React, ekranda neyin görüntülenmesi gerektiğini öğrenir. Daha sonra, Clock componentinin çıktısı ile eşleşecek şekilde DOM’u güncelleştirir.

3) Clock componentinin çıktısı DOM’a eklendiğinde, React, componentDidMount() fonksiyonunu çağırır. Her saniye çalışacak olan tick() fonksiyonunu timerIDde tutar.

4) Her saniye tarayıcı tick() fonksiyonunu çağırır. Clock componentinin içinde, geçerli saati içeren bir nesneyle setState()i çağırarak bir UI güncellemesi içinsetInterval() fonksiyonunu hazırlar. setState() çağrısı sayesinde React, statin değiştiğini bilir ve ekranda ne olması gerektiğini öğrenmek için tekrar render() eder. Bu sefer render() yöntemindeki this.state.date güncellenmiş olacaktır. React DOM’u buna göre günceller.

5) Clock componenti DOM’dan kaldırıldıysa, React, timerID durduğu anda componentWillUnmount() fonksiyonunu çağırır.

State'i Doğru Kullanmak

setState() hakkında bilmeniz gereken üç şey vardır.

State'i Doğrudan Değiştirmeyin

Örneğin, bu bir componenti yeniden oluşturmaz:

// Yanlış Kullanım
this.state.comment = 'Merhaba';

setState()i şu şekilde kullanın:

// Doğru Kullanım
this.setState({comment: 'Merhaba'});

this.statei atayabileceğiniz tek yer constructordür.

State Güncellemeleri Asenkron Olabilir

React, birden fazla setState() çağrısını performans için tek bir güncelleme haline getirebilir.

Çünkü this.props ve this.state eşzamansız olarak güncellenebilir.

Örneğin, bu sayaç güncellemesi başarısız olabilir:

// Yanlış Kullanım
this.setState({
  counter: this.state.counter + this.props.increment,
});

Bunu düzeltmek için, bir objeden ziyade bir fonksiyonu kabul eden ikinci bir setState() formunu kullanın. Bu işlev önceki durumu ilk argüman olarak, güncelleme ikinci argüman olarak uygulandığında sahne alacaktır:

// Doğru Kullanım
this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}));

Yukarıdaki örnekte ES6 ile gelen ok fonksiyonu kullandık fakat normal fonksiyonlarla da yapabilirdik:

// Doğru Kullanım
this.setState(function(prevState, props) {
  return {
    counter: prevState.counter + props.increment
  };
});
State'leri Toplu Güncelleştirmek

setState() öğesini çağırdığınızda, React state’i birleştirir. Bu yüzden birden fazla state aynı anda güncellenebilir.

constructor(props) {
  super(props);
  this.state = {
    posts: [],
    comments: []
  };
}

Ayrıca bunları ayrı setState() çağrılarıyla bağımsız olarakta güncelleyebilirsiniz:

componentDidMount() {
  fetchPosts().then(response => {
    this.setState({
      posts: response.posts
    });
  });

  fetchComments().then(response => {
    this.setState({
      comments: response.comments
    });
  });
}

setStatei kullandığımızda tüm state’ler değiştirilmez. Yalnızca belirtilen state güncellenir. Yani this.setState({comments}) şeklinde kullanıldığında sadece this.state.comments güncellenir, this.state.posts güncellenmez.

Sıradaki Eğitim: Click ve Change Olayları