State and Lifecycle

Bu səhifə React komponentin state və lifecycle anlayışlarını təqdim edir. Ətraflı komponent API kataloqu bu linkdə yerləşir.

Əvvəlki bölümlərin birindən işləyən saat nümunəsinə nəzər yetirək. Elementlərin render edilməsində biz UI dəyişməyin bir yolunu öyrəndik – ReactDOM.render() çağırmaqla:

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(
    element,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

CodePen-də bax

Bu bölümdə isə biz Clock komponentini inkapsulyasiya və təkrar istifadə etməyi öyrənəcəyik. Bu komponent öz taymerini quraşdıracaq və saniyədə bir dəfə özünü yeniləyəcək.

Bunun üçün əvvəlcə Clock komponentinə ayrılıqda nəzər yetirək:

function Clock(props) {
  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}

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

setInterval(tick, 1000);

CodePen-də bax

Bu komponentin önəmli çatışmazlığı var. Taymeri quraşdırma və özünü hər saniyə yeniləmə Clock komponentinin daxilində olmalıdır.

Məqsədimiz bu komponenti elə reallaşdırmaqdır ki, komponent özü özünü yeniləməyi bilsin:

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

Bunu yerinə yetirmək üçün Clock komponentinə “state” əlavə etmək lazımdır.

State prop-a bənzəyir, lakin komponent tərəfindən tam idarə olunur və yalnız onun daxilində əlçatandır.

Funksiyanın klasa çevirilməsi

Clock kimi funksional komponenti klas komponentinə 5 addımda çevirmək olar:

  1. İlkin komponentlə adı eyni olan, React.Component klasını genişləndirən ES6 klası yaradaq.

  2. Bu klasa render() adlı boş metod əlavə edək.

  3. Funksiyanın kodunu render() metoduna köçürək.

  4. render()-in içində props-u this.props ilə əvəzləyək.

  5. Boş qalmış funksiyanı silək.

class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

CodePen-də bax

Öncəliklə funksiya kimi təyin edilən Clock komponenti, indi klas kimi təyin edilmişdir.

render metodu hər dəfə yeniləmə baş tutduqda çağırılacaq. Lakin eyni DOM düyünü daxilində <Clock /> komponentini neçə dəfə istifadə etsək də, Clock klasının yalnız bir nüsxəsi istifadə olunacaq. Bu hal bizə lokal state və lifecycle kimi əlavə xüsusiyyətləri istifadə etmə imkanı verir.

Klasa lokal state əlavə edilməsi

date prop-unu state-ə üç addımda çevirək:

  1. render() metodunda this.props.date-i this.state.date ilə əvəz edək:
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
  1. this.state veriləninə ilkin dəyər təyin edən klas konstruktoru əlavə edək:
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

Diqqət yetirin ki, props arqumenti baza konstruktora da ötürülür:

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

Klas komponentləri həmişə baza konstruktoru props arqumentini ötürərək çağırmalıdırlar.

  1. <Clock /> elementindən date prop-unu silək:
ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

Bir qədər sonra taymerin kodunu komponentə geri əlavə edəcəyik.

Yekun nəticə belədir:

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

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

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

CodePen-də bax

İndi isə Clock komponentində taymer quraşdıraq və özünü hər saniyə yeniləməsini təmin edək.

Klasa lifecycle metodlarının əlavə edilməsi

Tərkibində çox sayda komponent olan applikasiyalarda həmin komponentlər silinəndə, onların tutduğu resursların azad olunması olduqca önəmlidir.

Komponentin DOM-da ilk dəfə render olunmasına “mounting” deyilir. Bizim məqsədimiz hər dəfə “mounting” baş tutanda taymeri quraşdırmaqdır.

Komponentin DOM-dan silinməsi isə React-da “unmounting” adlanır. Bu proses zamanı taymeri yaddaşdan silmək gərəkdir.

Mounting və unmounting zamanı istədiyimiz kodu icra edən metodları təyin edək:

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

  componentDidMount() {

  }

  componentWillUnmount() {

  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

Bu metodlara “lifecycle metodları” deyilir.

componentDidMount() metodu komponent DOM-da render olunduqdan dərhal sonra icra olunur. Taymeri quraşdırmaq üçün ən əlverişli yer buradır:

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

Diqqət yetirsək, taymerin ID-sini this də saxladığımızı görərsiniz (this.timerID).

this.props React tərəfindən quraşdırılır, this.state-in də xüsusi anlamı var. Bu ikisindən savayı klasa hər hansı məlumat saxlamaq üçün məlumat axımında iştirak etməyən başqa verilənlər əlavə etmək olar (taymerin id-si kimi).

componentWillUnmount() metodunda taymeri yaddaşdan siləcəyik:

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

Sonunda tick() metodunu yaradacağıq. Bu metodu Clock komponenti saniyədə bir dəfə çağıracaq.

tick() metodu this.setState() çağırmaqla komponentin lokal state-ni yeniləyəcək.

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

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

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

CodePen-də bax

İndi Clock hər saniyə dəyişir.

Gəlin bir daha hər şeyi təkrarlayaq və metodların çağırıldığı ardıcıllığa nəzər yetirək:

  1. <Clock /> komponenti ReactDOM.render() metoduna ötürüləndə React Clock klasının konstruktorunu çağırır. Bu komponent cari vaxtı göstərməlidir. Buna görə də this.state-i cari vaxt obyekti ilə inisializasiya edir.

  2. Daha sonra React Clock komponentinin render() metodunu çağırır. Belə React ekranda nə göstərmək lazım olduğunu öyrənir. Bundan da sonra DOM Clock-un render nəticəsinə uyğun olaraq yenilənir.

  3. Clock render olunub DOM-a əlavə olunanda componentDidMount() lifecycle metodu React tərəfindən çağırılır. Metodun içərisində Clock komponenti brauzerə taymer quraşdırmağı əmr edir. Bu taymer saniyədə bir dəfə tick() metodunu çağıracaq.

  4. Brauzer, gözləndiyi kimi, tick() metodunu hər saniyə çağırır. Bu metodun daxilində Clock komponenti, cari vaxtı ötürməklə, setState() metodunu çağırır. setState()-in çağırılması React-ə state-in dəyişdiyini xəbər verir. React buna görə ekranda nə olmasını dəqiqləşdirmək üçün yenidən render() metodunu icra edir. render() metodunda this.state.date fərqli olduğundan, renderin nəticəsi fərqli vaxt göstərəcək. React DOM-u buna uyğun dəyişir.

  5. Clock komponenti DOM-dan silinsə componentWillUnmount() lifecycle metodu React tərəfindən çağırılacaq, taymer dayandırılacaq.

State-in düzgün istifadə edilməsi

setState() haqqında bilməli olduğumuz üç şey var.

State-i birbaşa dəyişmək olmaz

Məsələn, bu kod komponenti yenidən render etməyəcək:

// Yanlış
this.state.comment = 'Hello';

Əvəzinə setState() istifadə etmək lazımdır:

// Düzgün
this.setState({comment: 'Hello'});

this.state-ə dəyər mənimsədilə biləcək yeganə yer konstruktordur.

State-in yenilənməsi asinxron ola bilər

React performansı təkmilləşdirmək üçün bir neçə setState() çağırışını qruplaşdıra bilər.

this.propsthis.state asinxron dəyişilə bildiyi üçün, onların sonrakı dəyərini hesablayanda indiki dəyərinə etibar etmək olmaz.

Məsələn, bu kod counter-i yeniləməkdə iflasa uğraya bilər.

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

Bu problemi düzəltmək üçün, setState()-in obyekt yerinə funksiya qəbul edən ikinci formasını istifadə etmək olar. Həmin funksiya birinci arqument kimi state-in əvvəlki dəyərini, ikinci arqument kimi isə propların yeniləmə zamanındakı dəyərini qəbul edir:

// Düzgün
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

Az öncəki misalda biz arrow funksiyasını istifadə etdik. Misal adi funksiyalar üçün də keçərlidir:

// Düzgün
this.setState(function(state, props) {
  return {
    counter: state.counter + props.increment
  };
});

State yeniləmələri birləşdirilir

setState() çağırılanda React ötürülən obyekti hazırki state-ə birləşdirir.

Misal üçün, state bir neçə sərbəst veriləndən ibarət ola bilər:

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

Həmin verilənlər müstəqil olaraq setState() vasitəsi ilə yenilənə bilər:

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

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

Birləşdirmə dayazdır (shallow), yəni this.setState({comments}) çağırılanda this.state.posts-u dəyişmir, amma this.state.comments-i tamamilə əvəz edir.

Verilənlərin aşağı istiqamətdə axını

Nə valideyn, nə də uşaq komponenti digər komponentin state-inin olub olmadığını bilməyə məcbur deyil. Həmin komponentin funksiya və ya klas kimi təyin olmağı da onlar üçün önəmli deyil.

Məhz buna görə state lokal və ya inkapsulyasiya olunmuş adlanır. Yalnız məxsus olduğu komponent daxilində əlçatandır, digər komponentlər onu görmür.

Komponent öz state-ini uşaq komponentlərinə (aşağı istiqamətdə) props kimi ötürə bilər:

<h2>It is {this.state.date.toLocaleTimeString()}.</h2>

Bu həmçinin istifadəçinin yaratdığı komponentlər üçün keçərlidir:

<FormattedDate date={this.state.date} />

FormattedDate komponenti date-i props kimi qəbul edəcək. Lakin onun Clock-un state-i, Clock-un propları və ya manual olaraq daxil olduğundan xəbər tutmayacaq:

function FormattedDate(props) {
  return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}

CodePen-də bax

Buna “yuxarıdan-aşağı” (“top-down”) və ya “birtərəfli” (“unidirectional”) verilənlər axını deyilir. Hər bir state bir komponentə məxsusdur. Həmin state-in törəmələri yalnız komponentlər ağacında “aşağıda” olan komponentlərə təsir göstərə bilər.

Əgər komponent ağacını prop-ların şəlaləsi kimi təsəvvür etsək, hər bir komponentin state-i şəlaləyə qovuşan yeni su mənbəsidir. Hansıki təsadüfi bir yerdə şəlaləyə bitişir, amma yenə də aşağı axır.

Bütün komponentlərin izolyasiya olduğunu göstərmək üçün tərkibində üç <Clock> olan App komponenti yaradaq:

function App() {
  return (
    <div>
      <Clock />
      <Clock />
      <Clock />
    </div>
  );
}

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

CodePen-də bax

Hər bir Clock öz taymerini quraşdırır və sərbəst yenilənir.

React applikasiyalarında komponentin state-nin olub olmaması gələcəkdə dəyişə bilən daxili detal sayılır. State-i olan komponentlər state-i olmayan komponentlərin içində istifadə oluna bilər. Əks bəyanətdə doğrudur.