State dan Lifecycle

Halaman ini akan mengenalkan tentang konsep dari state dan lifecycle di sebuah komponen React. Anda bisa menemukan referensi detail API komponen disini.

Pertimbangkan contoh detak jam dari salah satu bagian sebelumnya. Di Rendering Element, kita baru saja mempelajari satu cara untuk memperbarui UI. Kita memanggil ReactDOM.render() untuk mengubah hasil render:

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

setInterval(tick, 1000);

Coba di CodePen

Di bagian ini, Kita akan belajar cara membuat komponen Clock benar-benar dapat digunakan kembali dan terenkapsulasi. Ini akan mengatur timer-nya sendiri dan memperbaruinya setiap detik.

Kita dapat memulai dengan mengenkapsulasi bagaimana jam terlihat:

function Clock(props) {
  return (
    <div>
      <h1>Halo, dunia!</h1>
      <h2>Ini {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}

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

setInterval(tick, 1000);

Coba di CodePen

Namun, ia melewatkan persyaratan penting: faktanya bahwa Clock mengatur timer dan memperbarui UI setiap detik harus merupakan detail implementasi dari Clock.

Idealnya kita butuh untuk menulis ini sekali dan Clock dapat memperbarui dirinya sendiri:

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

Untuk mengimplementasikan ini, kita perlu untuk menambahkan “state” ke komponen Clock.

State mirip dengan props, tetapi bersifat pribadi dan sepenuhnya dikendalikan oleh komponen.

Mengubah Sebuah Fungsi ke Sebuah Kelas

Anda bisa mengubah sebuah function component seperti Clock ke sebuah kelas dalam lima langkah:

  1. Buat sebuah kelas ES6, dengan nama yang sama, yang diturunkan dari React.Component.

  2. Tambah sebuah method kosong yang bernama render().

  3. Pindahkan isi fungsi ke dalam method render().

  4. Ganti props dengan this.props di dalam render().

  5. Hapus deklarasi fungsi kosong yang tersisa.

class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Halo, dunia!</h1>
        <h2>Ini {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

Coba di CodePen

Clock sekarang terdefinisikan sebagai sebuah kelas daripada fungsi

method render akan dipanggil setiap waktu ketika pembaruan terjadi, tapi selama kita me-render <Clock /> simpul DOM yang sama, hanya satu instansi dari kelas Clock yang akan digunakan. Ini memungkinkan kita untuk mengunakan fitur tambahan seperti local state dan lifecycle method.

Menambahkan State lokal ke sebuah kelas

Kita akan memindahkan date dari props ke state dalam tiga langkah:

  1. Ubah this.props.date_ dengan this.state.date di method render():
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Halo, dunia!</h1>
        <h2>Ini {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
  1. Tambah sebuah konstruktor kelas yang menetapkan nilai awal this.state:
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Halo, dunia!</h1>
        <h2>Ini {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

Perhatikan bagaimana kami meneruskan props ke konstruktor dasar:

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

Kelas komponen harus selalu memanggil ke konstruktor dasar dengan props.

  1. Hapus properti date dari elemen <Clock />:
ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

Kita nanti akan menambahkan kode timer kembali ke komponen itu sendiri.

Hasilnya akan terlihat seperti ini:

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

  render() {
    return (
      <div>
        <h1>Halo, dunia!</h1>
        <h2>Ini {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

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

Coba di CodePen

Selanjutnya, kami akan membuat Clock mengatur timer sendiri dan memperbarui dirinya sendiri setiap detik.

Menambah Method Lifecycle ke Kelas

Dalam aplikasi dengan banyak komponen, sangat penting untuk membebaskan sumber daya yang diambil oleh komponen ketika mereka dihancurkan.

Kami ingin mengatur timer setiap kali Clock di-render di DOM untuk pertama kalinya . Ini disebut “mounting” di React.

Kita juga ingin untuk menghapus timer tersebut setiap kali DOM yang diproduksi oleh Clock dihapus. Ini disebut “unmounting” di React.

Kita dapat mendeklarasi method spesial di kelas komponen untuk menjalankan beberapa kode ketika sebuah komponen mounts dan unmounts:

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

  componentDidMount() {

  }

  componentWillUnmount() {

  }

  render() {
    return (
      <div>
        <h1>Halo, dunia!</h1>
        <h2>Ini {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

Method ini disebut ”lifecycle method“.

Method componentDidMount() berjalan setelah hasil komponen sudah ter-render di DOM. Ini adalah tempat yang bagus untuk mengatur timer:

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

Perhatikan bagaimana kami menyimpan ID timer langsung pada this (this.timerID).

Ketika this.props diatur oleh React sendiri dan this.state punya arti spesial, Anda dapat dengan bebas untuk menambah field tambahan di kelas secara manual jika Anda butuh untuk menyimpan sesuatu yang tidak ikut berpartisipasi di alur data (seperti ID timer).

Kita akan menghapus timer di method lifecycle componentWillUnmount():

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

Akhirnya, kita akan mengimplementasi sebuah method bernama tick() yang dijalankan oleh komponen Clock setiap detik.

Itu akan mengunakan this.setState() untuk menjadwal pembaruan ke state lokal milik komponen:

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, dunia!</h1>
        <h2>Ini {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

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

Coba di CodePen

Sekarang jam akan berdetak setiap waktu.

Mari kita rekap dengan cepat apa yang terjadi dan urutan method yang dipanggil:

  1. Ketika <Clock /> diberikan ke ReactDOM.render(), React memanggil konstruktor dari komponen Clock. Ketika Clock perlu untuk menampilkan waktu saat ini, dia menginisialisai this.state dengan sebuah obyek termasuk waktu saat ini. Kita nantinya akan memperbarui status ini.

  2. React kemudian memanggil method render() milik komponen Clock. Beginilah cara React belajar apa yang harusnya ditampilkan di layar. React kemudian memperbarui DOM untuk mencocokan hasil render Clock.

  3. Ketika hasil Clock dimasukkan ke dalam DOM, React memanggil method lifecycle componentDidMount(). Didalamnya, komponen Clock menyuruh browser untuk mengatur sebuah timer untuk memanggil method tick() milik komponen sekali per detik.

  4. Setiap detik browser memanggil method tick(). Didalamnya, komponen Clock menjadwal pembaruan UI dengan memanggil setState() dengan sebuah obyek berisi waktu sekarang. Berkat panggilan setState(), React mengetahui state sudah berubah, dan memanggil method render() lagi untuk mempelajari apa yang harusnya ada di layar. Kali ini, this.state.date di render() method akan berbeda, dan jadi hasil render akan mencakup waktu yang diperbarui. React telah memperbarui DOM dengan sesuai.

  5. Jika komponen Clock dihapus dari DOM, React memanggil method lifecycle componentWillUnmount() jadi timer akan berhenti.

Menggunakan State Dengan Benar

Ada tiga hal yang harus Anda ketahui tentang setState().

Jangan mengubah State Secara Langsung

Sebagai contoh, ini tidak akan me-render komponen :

// Salah
this.state.comment = 'Halo';

Sebagai gantinya, gunakan setState():

// Benar
this.setState({comment: 'Halo'});

Satu-satunya tempat di mana Anda dapat menetapkan this.state adalah di konstruktor.

Pembaruan State Mungkin Asynchronous

React dapat mengelompokkan beberapa panggilan setState() menjadi satu untuk kinerja lebih baik.

Karena this.props dan this.state mungkin diperbarui secara asynchronous, Anda seharusnya tidak mengandalkan nilai-nilai tersebut untuk menghitung State berikutnya.

Sebagai contoh, kode ini mungkin gagal untuk memperbarui penghitung:

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

Untuk memperbaikinya, pakai bentuk kedua dari setState() yang menerima fungsi daripada sebuah objek. Fungsi itu akan menerima state sebelumnya sebagai argumen pertama, dan props pada waktu itu pembaruanya di terapkan ke argumen kedua:

// Correct
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

Kita menggunakan arrow function diatas, tetapi juga bisa menggunakan fungsi biasa:

// Benar
this.setState(function(state, props) {
  return {
    counter: state.counter + props.increment
  };
});

Pembaruan State Digabungkan

Ketika Anda memanggil setState(), React mengabungkan obyek yang anda siapkan ke state saat ini.

Sebagai contoh, state anda mungkin menganduk beberapa variabel independen:

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

Kemudian Anda dapat memperbarui mereka secara terpisah dengan memanggil setState() yang terpisah:

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

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

Pengabunganya dangkal, jadi this.setState({comments}) meninggalkan this.state.posts dengan utuh, tetapi sepenuhnya menggantikan this.state.comments.

Data Mengalir ke Bawah

Baik komponen induk maupun anak tidak tahu apakah komponen tertentu mengandung state atau tidak, dan mereka tidak seharusnya peduli apakah itu didefinisikan sebagai fungsi atau kelas.

Inilah sebabnya mengapa state kadang disebut lokal atau terenkapsulasi. Itu tidak dapat diakses oleh komponen apa pun selain dari yang memiliki dan menetapkannya.

Sebuah komponen dapat memilih untuk menurunkan state sebagai props ke komponen turunannya:

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

Ini juga berfungsi untuk komponen yang ditentukan pengguna:

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

Komponen FormattedDate akan menerima date props-nya dan tidak akan tahu apakah itu datang dari state milik Clock, dari props milik Clock, atau itu diketik dengan tangan:

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

Coba di CodePen

Ini umumnya memanggil aliran data “atas-bawah” atau “searah”. Semua state selalu dimiliki oleh komponen spesifik, dan semua data atau UI yang berasal dari state tersebut hanya bisa mempengaruhi pohon komponen “di bawah” mereka.

Jika Anda membanyangkan pohon komponen sebagai air terjun dari props, tiap state milik komponen seperti sebuah sumber air yang bergabung dengannya pada titik acak tetapi juga mengalir ke bawah.

Untuk menunjukkan jika semua komponen benar-benar terisolasi, Kita dapat membuat sebuah komponen App yang me-render tiga <Clock>:

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

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

Coba di CodePen

Setiap Clock membuat timer-nya sendiri dan memperbaruinya secara independen.

Di aplikasi React, apakah suatu komponen memiliki state atau tidak itu dianggap sebagai detail implementasi komponen yang dapat berubah dari waktu ke waktu. Anda dapat menggunakan komponen tanpa state di dalam komponen dengan state, dan sebaliknya.