Bileşenlere Fonksiyon Gönderilmesi

Bir bileşene nasıl olay yöneticisi gönderebilirim? (onClick gibi)

Olay yöneticileri ve diğer fonksiyonlar alt bileşenlere prop olarak aktarılabilir:

<button onClick={this.handleClick}>

Eğer olay yöneticisi içinden bir üst bileşene erişmeniz gerekiyorsa, fonksiyonu aynı zamanda bileşene bağlamanız gerekir. (aşağıya bakın).

Bir fonksiyonu bir bileşene nasıl bağlarım?

Fonksiyonların this.props ve this.state gibi bileşen niteliklerine erişmelerini sağlamak için çeşitli yollar vardır. Bunlar hangi sözdizimi ve yapı adımlarını kullandığınıza bağlı olarak değişebilir.

Yapılandırıcı içinde bağlamak (ES2015)

class Foo extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    console.log('Click happened');
  }
  render() {
    return <button onClick={this.handleClick}>Click Me</button>;
  }
}

Sınıf Özellikleri

class Foo extends Component {
  // Not: bu sözdizimi deneyseldir ve henüz standartlaştırılmamıştır.
  handleClick = () => {
    console.log('Click happened');
  }
  render() {
    return <button onClick={this.handleClick}>Click Me</button>;
  }
}

Render içerisinde bağlamak

class Foo extends Component {
  handleClick() {
    console.log('Click happened');
  }
  render() {
    return <button onClick={this.handleClick.bind(this)}>Click Me</button>;
  }
}

Not:

Render içerisinde Function.prototype.bind kullanmak, bileşen her render edildiğinde yeni bir fonksiyon oluşturur. Bu da performans kayıplarına sebep olabilir.

Render içerisinde ok fonksiyonu

class Foo extends Component {
  handleClick() {
    console.log('Click happened');
  }
  render() {
    return <button onClick={() => this.handleClick()}>Click Me</button>;
  }
}

Not:

Render içerisinde ok fonksiyonu kullanmak, bileşen her render edildiğinde yeni bir fonksiyon oluşturur. Bu da, katı bir kimlik karşılaştırmasına dayalı performans kayıplarına yol açabilir.

Render metodu içerisinde ok fonksiyonu kullanmak doğru mudur?

Genel olarak konuşursak, evet doğrudur ve genellikle parametreleri geri çağırma fonksiyonlarına (callback) göndermenin en kolay yoludur.

Eğer performans sorunlarınız varsa, optimize edin!

Fonksiyonları bağlamak neden gerekli?

JavaScript’te aşağıdaki iki kod parçacığı aynı değildir.

obj.method();
var method = obj.method;
method();

Metodları bağlamak, ikinci kod parçacığının birincisiyle aynı şekilde çalışmasını sağlar.

React’te genellikle sadece diğer bileşenlere gönderdiğiniz metodları bağlamanız gerekir. Örneğin <button onClick={this.handleClick}>, this.handleClick fonksiyonunu göndermektedir. Bu yüzden bu metodu bağlamanız gerekir. Ancak render metodunu veya diğer yaşam döngüsü metodlarını bağlamak gereksizdir çünkü bunlar diğer bileşenlere aktarılmazlar.

Yehuda Katz’ın bu yazısı bağlamanın ne olduğunu ve fonksiyonların JavaScript’te nasıl çalıştıklarını detaylı olarak açıklamaktadır.

Neden fonksiyonum, bileşen her render olduğunda yeniden çağırılıyor?

Fonksiyonu bir bileşene aktardığınızda, onu çağırmadığınızdan emin olun.

render() {
  // Yanlış: handleClick referans olarak aktarılmak yerine çağırılıyor!
  return <button onClick={this.handleClick()}>Click Me</button>
}

Bunun yerine, fonksiyonun kendisini aktarın (parantezler olmadan):

render() {
  // Doğru: handleClick referans olarak aktarılıyor!
  return <button onClick={this.handleClick}>Click Me</button>
}

Bir olay yöneticisine veya geri çağırma fonksiyonuna nasıl parametre gönderirim?

Bir olay yöneticisini sarmalamak ve parametre göndermek için ok fonksiyonunu kullanabilirsiniz:

<button onClick={() => this.handleClick(id)} />

Bu .bind‘ı çağırmakla aynıdır:

<button onClick={this.handleClick.bind(this, id)} />

Örnek: Parametreleri ok fonksiyonu kullanarak göndermek

const A = 65 // ASCII karakter kodu

class Alphabet extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
    this.state = {
      justClicked: null,
      letters: Array.from({length: 26}, (_, i) => String.fromCharCode(A + i))
    };
  }
  handleClick(letter) {
    this.setState({ justClicked: letter });
  }
  render() {
    return (
      <div>
        Just clicked: {this.state.justClicked}
        <ul>
          {this.state.letters.map(letter =>
            <li key={letter} onClick={() => this.handleClick(letter)}>
              {letter}
            </li>
          )}
        </ul>
      </div>
    )
  }
}

Örnek: Veri niteliklerini kullanarak parametre göndermek

Alternatif olarak, olay yöneticileri için gereken verileri saklamak için DOM API’lerini kullanabilirsiniz. Çok sayıda öğeyi optimize etmeniz gerekiyorsa veya React.PureComponent eşitlik kontrollerine dayanan bir render ağacına sahipseniz bu yaklaşımı göz önünde bulundurun.

const A = 65 // ASCII karakter kodu

class Alphabet extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
    this.state = {
      justClicked: null,
      letters: Array.from({length: 26}, (_, i) => String.fromCharCode(A + i))
    };
  }

  handleClick(e) {
    this.setState({
      justClicked: e.target.dataset.letter
    });
  }

  render() {
    return (
      <div>
        Just clicked: {this.state.justClicked}
        <ul>
          {this.state.letters.map(letter =>
            <li key={letter} data-letter={letter} onClick={this.handleClick}>
              {letter}
            </li>
          )}
        </ul>
      </div>
    )
  }
}

Bir fonksiyonun arka arkaya çok defa veya çok hızlı bir şekilde çağırılmasını nasıl önleyebilirim?

onClick veya onScroll gibi bir olay yöneticiniz varsa ve geri çağırılmanın çok hızlı bir şekilde başlatılmasını istemiyorsanız, aşağıdaki yöntemlerle yürütülme sıklığını sınırlayabilirsiniz.

  • daraltma: değişiklikleri zamana dayalı bir frekansa göre örnekler (örneğin _.throttle)
  • sıçrama önleme: belirli bir süre işlem yapılmadığında tetiklenir (örneğin _.debounce)
  • requestAnimationFrame daraltması: değişiklikleri requestAnimationFrame‘e dayanarak örnekler (örneğin raf-schd)

Daraltma ve sıçrama önleme karşılaştırması için bu görselleştirme‘ye bakınız.

Not:

_.debounce, _.throttle ve raf-schd geciken geri çağırmaları iptal etmek için bir cancel metodu sağlarlar. Bu metodu componentWillUnmount içinden çağırmalı ya da bileşenin hala geciken fonksiyon içinde bağlı olduğundan emin olmalısınız.

Daraltma

Daraltma, bir fonksiyonun belirli bir zaman aralığında bir defadan fazla çağırılmasını önler. Aşağıdaki örnekte bir “tıklama” olay yöneticisinin saniyede birden çok kez çağırılması daraltma yöntemiyle önlenmiştir.

import throttle from 'lodash.throttle';

class LoadMoreButton extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
    this.handleClickThrottled = throttle(this.handleClick, 1000);
  }

  componentWillUnmount() {
    this.handleClickThrottled.cancel();
  }

  render() {
    return <button onClick={this.handleClickThrottled}>Load More</button>;
  }

  handleClick() {
    this.props.loadMore();
  }
}

Sıçrama önleme

Sıçrama önleme, bir fonksiyonun son çağırıldığı andan itibaren belirli bir süre geçinceye kadar tekrar yürütülmemesini sağlar. Bu, çok hızlı bir şekilde tetiklenebilecek bir olay yöneticisine (örneğin klavye veya kaydırma olaylarına) yanıt olarak masraflı hesaplamalar yapmanız gerektiği zamanlarda çok yararlı olabilir. Aşağıdaki örnekte olay yöneticisi, 250ms boyunca metin girişi olmadığında çağırılmak üzere kurulmuştur.

import debounce from 'lodash.debounce';

class Searchbox extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.emitChangeDebounced = debounce(this.emitChange, 250);
  }

  componentWillUnmount() {
    this.emitChangeDebounced.cancel();
  }

  render() {
    return (
      <input
        type="text"
        onChange={this.handleChange}
        placeholder="Search..."
        defaultValue={this.props.value}
      />
    );
  }

  handleChange(e) {
    // React olayları birleştirir, bu yüzden değeri, zıplama önleyiciden önce okuyoruz.
    // Alternatif olarak `event.persist()`'i çağırabilir ve tüm olayı aktarabilirdik.
    // Daha fazla bilgi için şuraya göz atın: reactjs.org/docs/events.html#event-pooling
    this.emitChangeDebounced(e.target.value);
  }

  emitChange(value) {
    this.props.onChange(value);
  }
}

requestAnimationFrame daraltması

requestAnimationFrame, render edilme performansını artırmak için bir metodun tarayıcıda en uygun zamanda çalıştırılmak üzere sıraya konulması işlemidir. requestAnimationFrame ile sıraya konulan bir fonksiyon, bir sonraki çerçevede çalıştırılır. Tarayıcılar, saniyede 60 kare (60 fps) görüntü sunulduğundan emin olmak için yüksek çaba sarf eder. Ancak bunu başaramadıklarında, saniyede gösterilen kare sayısı doğal olarak limitlenecektir. Örneğin bir cihaz saniyede sadece 30 kare işleyebilecek kapasitedeyse, saniyede maksimum 30 kare görüntü gösterebilir. requestAnimationFrame daraltması, bir saniyede 60’tan fazla güncelleme yapılmasını önleyen kullanışlı bir tekniktir. Eğer saniyede 100 güncelleme yapıyorsanız, kullanıcılarınızın zaten göremeyeceği ama tarayıcıya ek yük bindiren gereksiz işlemler yapıyor olabilirsiniz.

Not:

Bu tekniğin kullanılması sadece bir çerçevede yayınlanan son değeri yakalayacaktır. Bu optimizasyonun nasıl çalıştığının bir örneğini MDN‘de görebilirsiniz.

import rafSchedule from 'raf-schd';

class ScrollListener extends React.Component {
  constructor(props) {
    super(props);

    this.handleScroll = this.handleScroll.bind(this);

    // Güncellemeleri planlamak için yeni bir fonksiyon oluşturulur.
    this.scheduleUpdate = rafSchedule(
      point => this.props.onScroll(point)
    );
  }

  handleScroll(e) {
    // Bir kaydırma etkinliği alındığında, bir güncelleme planlanır.
    // Bir çerçeve içinde birçok güncelleme alınırsa, sadece en son değer yayınlanır.
    this.scheduleUpdate({ x: e.clientX, y: e.clientY });
  }

  componentWillUnmount() {
    // Bileşen umnount edileceğinden, bekleyen tüm güncellemeler iptal edilir.
    this.scheduleUpdate.cancel();
  }

  render() {
    return (
      <div
        style={{ overflow: 'scroll' }}
        onScroll={this.handleScroll}
      >
        <img src="/my-huge-image.jpg" />
      </div>
    );
  }
}

Sıklık sınırlamasının test edilmesi

Sıklık sınırlaması (rate limiting) kodunuzun doğru çalışıp çalışmadığını test ederken, zamanı ileri sarabilme özelliğine sahip olmak çok yardımcı olacaktır. Eğer jest kullanıyorsanız, zamanı ileri sarmak için mock timers kullanabilirsiniz. Eğer requestAnimationFrame daraltması kullanıyorsanız, raf-stub animasyon çerçevelerini ileri sarmak için kullanışlı bir araç olabilir.