Mengoptimalkan Performa

Secara internal, React menggunakan beberapa teknik cermat untuk meminimalkan jumlah operasi DOM boros yang diperlukan untuk memperbarui UI. Bagi sebagian besar aplikasi, menggunakan React akan menghasilkan antarmuka pengguna yang cepat tanpa harus melakukan pekerjaan tambahan untuk mengoptimalkan performa secara spesifik. Meskipun demikian, ada beberapa cara untuk mempercepat aplikasi React Anda.

Menggunakan Versi Produksi

Apabila Anda melakukan benchmarking atau mengalami masalah performa di aplikasi React Anda, pastikan Anda mencobanya dengan versi produksi yang telah di minified.

Secara default, React mengandung banyak pesan peringatan. Peringatan-peringatan ini sangat berguna dalam pengembangan aplikasi. Namun, pesan-pesan ini membuat React menjadi lebih besar dan lambat sehingga Anda harus memastikan menggunakan versi produksi ketika Anda men-deploy aplikasi.

Jika Anda tidak yakin apakah proses build Anda sudah diatur dengan benar atau belum, Anda bisa mengeceknya dengan menginstal React Developer Tools untuk Chrome. Jika Anda mengunjungi situs yang menggunakan React dalam versi produksi, ikon ekstensi akan memiliki latar belakang gelap:

React DevTools dalam situs dengan versi produksi React

Jika Anda mengunjungi situs yang menggunakan React dalam mode pengembangan, ikon ekstensi akan memiliki latar belakang merah:

React DevTools dalam situs dengan versi pengembangan React.

Anda diharapkan menggunakan mode pengembangan ketika mengerjakan aplikasi Anda, dan mode produksi ketika men-deploy aplikasi Anda kepada pengguna.

Anda dapat menemukan instruksi untuk membangun aplikasi Anda untuk mode produksi di bawah.

Create React App

Jika proyek Anda dibangun menggunakan Create React App, jalankan:

npm run build

Ini akan membuat versi produksi untuk aplikasi Anda di dalam folder build/ di proyek Anda.

Ingat bahwa ini hanya diperlukan sebelum men-deploy ke produksi. Untuk pengembangan normal, gunakan npm start.

Single-File Build

Kami menyediakan versi siap produksi dari React dan React DOM sebagai file tunggal:

<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

Ingat bahwa hanya file React yang berakhir dengan .production.min.js yang layak digunakan untuk produksi.

Brunch

Untuk build produksi yang efisien menggunakan Brunch, instal plugin terser-brunch:

# Jika menggunakan npm
npm install --save-dev terser-brunch

# Jika menggunakan Yarn
yarn add --dev terser-brunch

Kemudian, untuk membuat build produksi, tambahkan flag -p ke command build:

brunch build -p

Ingat bahwa Anda hanya perlu melakukan ini di build produksi. Anda tidak perlu mengoper flag -p atau mengaplikasikan plugin ini dalam pengembangan, karena akan menyembunyikan peringatan React yang berguna dan membuat waktu build lebih lambat.

Browserify

Untuk build produksi yang efisien menggunakan Browserify, instal plugin-plugin berikut:

# Jika menggunakan npm
npm install --save-dev envify terser uglifyify 

# Jika menggunakan Yarn
yarn add --dev envify terser uglifyify 

Untuk membangun versi produksi, pastikan Anda menambahkan transform berikut (urutan ini penting):

  • Transform envify memastikan pengaturan build environment telah tepat. Aplikasikan secara global (-g).
  • Transform uglifyify menghapus import-import pengembangan. Aplikasikan secara global juga (-g).
  • Akhirnya, bundel akhir di-pipe ke terser untuk proses mangling (baca alasannya).

Contoh:

browserify ./index.js \
  -g [ envify --NODE_ENV production ] \
  -g uglifyify \
  | terser --compress --mangle > ./bundle.js

Ingat bahwa Anda hanya perlu melakukan ini di build produksi. Anda tidak perlu mengaplikasikan mengaplikasikan plugin ini dalam pengembangan, karena akan menyembunyikan peringatan React yang berguna dan membuat waktu build lebih lambat.

Rollup

Untuk build produksi yang efisien menggunakan Rollup, instal plugin-plugin berikut:

# Jika menggunakan npm
npm install --save-dev rollup-plugin-commonjs rollup-plugin-replace rollup-plugin-terser

# Jika menggunakan Yarn
yarn add --dev rollup-plugin-commonjs rollup-plugin-replace rollup-plugin-terser

Untuk membangun versi produksi, pastikan Anda menambahkan plugin-plugin berikut (urutan ini penting):

  • Plugin replace memastikan pengaturan build environment telah tepat.
  • Plugin commonjs mengaktifkan dukungan CommonJS di Rollup.
  • Plugin terser mengkompresikan dan melakukan proses mangling pada bundel akhir.
plugins: [
  // ...
  require('rollup-plugin-replace')({
    'process.env.NODE_ENV': JSON.stringify('production')
  }),
  require('rollup-plugin-commonjs')(),
  require('rollup-plugin-terser')(),
  // ...
]

Lihat gist ini untuk contoh pengaturan lengkap.

Ingat bahwa Anda hanya perlu melakukan ini di build produksi. Anda tidak perlu mengaplikasikan plugin terser atau plugin replace dengan value 'production' dalam pengembangan karena akan menyembunyikan peringatan React yang berguna dan membuat waktu build lebih lambat.

webpack

Catatan:

Jika Anda menggunakan Create React App, ikuti instruksi di atas.
Bagian ini hanya relevan jika Anda melakukan konfigurasi webpack sendiri.

Webpack v4+ akan me-minify kode Anda secara default di mode produksi.

const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  mode: 'production',
  optimization: {
    minimizer: [new TerserPlugin({ /* additional options here */ })],
  },
};

Anda dapat mempelajari ini lebih lanjut di dokumentasi webpack.

Ingat bahwa Anda hanya perlu melakukan ini di build produksi. Anda tidak perlu mengaplikasikan TerserPlugin dalam pengembangan karena akan menyembunyikan peringatan React yang berguna dan membuat waktu build lebih lambat.

Memprofil Komponen dengan Tab Performance Chrome

Dalam mode pengembangan, Anda dapat memvisualisasikan bagaimana komponen terpasang, diperbarui, dan dilepas, mengunakan perangkat performa di peramban web yang mendukungnya. Sebagai contoh:

Komponen React di timeline Chrome

Cara melakukannya di Chrome:

  1. Matikan seluruh ekstensi Chrome secara sementara, termasuk React DevTools. Ekstensi tambahan dapat mengubah hasil uji performa!

  2. Pastikan Anda menjalankan aplikasi dalam mode pengembangan.

  3. Buka tab Performance pada Chrome DevTools dan tekan Record untuk merekam.

  4. Lakukan aksi-aksi yang ingin Anda profil. Jangan merekan lebih dari 20 detik, ini akan membuat Chrome hang.

  5. Berhenti merekam.

  6. Event React akan dikelompokkan dalam label User Timing.

Untuk panduan lebih lengkap, baca artikel ini oleh Ben Schwarz.

Perlu dicatat bahwa nilai-nilai ini adalah relatif sehingga komponen akan me-render lebih cepat di mode produksi. Tetap saja, ini akan membantu Anda untuk mengetahui ketika bagian UI yang tidak relevan diperbarui secara tidak sengaja, dan seberapa dalam dan sering pembaruan UI terjadi.

Saat ini hanya Chrome, Edge, and IE yang mendukung fitur ini, tapi kita menggunakan User Timing API yang terstandarisasi sehingga kami memperkirakan peramban web lain akan menambahkan dukungan terhadap API tersebut.

Memprofil Komponen dengan Profiler DevTools

react-dom 16.5+ dan react-native 0.57+ menyediakan kemampuan memprofil tambahan dalam mode pengembangan dengan Profiler React DevTools. Sebuah ikhtisar mengenai profiler tersebut dapat ditemukan dalam postingan blog “Introducing the React Profiler”. Panduan video profiler ini juga tersedia di YouTube.

Jika Anda belum menginstal React DevTools, Anda dapat menemukannya di sini:

Catatan

Versi bundel react-dom yang dapat memprofil versi produksi juga tersedia di react-dom/profiling. Baca lebih lanjut mengenai cara penggunaan bundel ini di fb.me/react-profiling

Memvirtualisasi List Panjang

Jika aplikasi Anda me-render list data yang panjang (ratusan atau ribuan baris), kami menyarankan menggunakan teknik “windowing”. Teknik ini hanya me-render bagian kecil dari baris-baris data Anda dalam waktu tertentu, dan dapat mengurangi waktu untuk me-render ulang komponen Anda serta jumlah simpul DOM yang dibuat secara signifikan.

react-window dan react-virtualized adalah library windowing paling populer. Library-library ini menyediakan beberapa komponen untuk menampilkan data-data dalam bentuk list, grid, dan tabular. Anda juga dapat membuat komponen windowing versi Anda sendiri, seperti yang dilakukan Twitter, jika Anda ingin sesuatu yang lebih dikhususkan untuk use case aplikasi Anda.

Menghindari Rekonsiliasi

React membangun dan memelihara representasi internal dari UI yang di-render. Representasi ini berisi elemen-elemen React yang dikembalikan dari komponen Anda. Representasi ini memungkinkan React menghindari membuat simpul DOM baru dan mengakses simpul DOM yang sudah ada diluar keperluan, karena mereka bisa lebih lambat daripada operasi dalam obyek JavaScript. Terkadan hal ini disebut sebagai “virtual DOM”, namun dapat juga bekerja secara sama di React Native.

Ketika props atau state komponen berubah, React memutuskan apakah pembaruan DOM yang sesungguhnya diperlukan dengan membandingkan elemen yang baru dikembalikan dengan elemen yang sebelumnya di-render. Ketika keduanya tidak sama, React akan memperbarui DOM.

Meskipun React hanya memperbarui simpul DOM yang berubah, me-render ulang tetap memerlukan waktu tambahan. Dalam banyak kasus ini tidak menjadi masalah, namun ketika ada perlambatan yang signifikan, Anda dapat mempercepatnya dengan meng-override lifecycle shouldComponentUpdate, yang dijalankan sebelum proses render ulang dimulai. Implementasi bawaan fungsi ini mengembalikan true, yang membuat React melakukan pembaruan tersebut:

shouldComponentUpdate(nextProps, nextState) {
  return true;
}

Jika Anda tahu bahwa dalam beberapa kasus komponen Anda tidak perlu diperbarui, Anda dapat mengembalikan false dari shouldComponentUpdate, untuk melewati seluruh proses rendering, termasuk memanggil render() dalam komponen ini dan komponen-komponen didalamnya.

Dalam banyak kasus, alih-alih menuliskan shouldComponentUpdate() secara manual, Anda dapat meng-inherit dari React.PureComponent. Ini sama seperti mengimplementasikan shouldComponentUpdate() dengan perbandingan dangkal dari props dan state sekarang.

Cara Kerja shouldComponentUpdate

Berikut adalah subdiagram beberapa komponen. Dalam setiap komponen, SCU mengindikasikan apa yang dikembalikan shouldComponentUpdate, dan vDOMEq mengindikasikan apakah elemen-elemen React yang di-render ekuivalen. Akhirnya, warna lingkaran mengindikasikan apakah komponen tersebut harus direkonsiliasikan atau tidak.

should component update

Karena shouldComponentUpdate mengembalikan false pada subdiagram yang mengakar pada C2, React tidak akan me-render C2, dan dengan demikian tidak perlu menjalankan shouldComponentUpdate di C4 dan C5.

Untuk C1 dan C3, shouldComponentUpdate mengembalikan true, sehingga React harus menelusuri cabang komponen dan mengeceknya. Untuk C6 shouldComponentUpdate mengembalikan true, dan karena elemen yang di-render tidak ekuivalen React harus memperbarui DOM.

Kasus menarik yang terakhir adalah C8. React harus me-render komponen ini, tapi karena elemen-elemen React yang dikembalikan sama dengan elemen yang sebelumnya di-render, ia tidak harus memperbarui DOM.

Perlu dicatat bahwa React hanya perlu melakukan mutasi DOM untuk C6, yang tidak dapat dihindari. Untuk C8, ia menghindari mutasi DOM dengan membandingkan elemen-elemen React yang di-render, dan untuk subdiagram komponen C2 dan C7, ia bahkan tidak perlu membandingkan elemen-elemennya karena kita sudah menghindarinya di dalam shouldComponentUpdate, jadi render tidak dipanggil.

Contoh

Jika satu-satunya cara komponen Anda berubah adalah ketika variabel props.color atau state.count berubah, Anda dapat mengeceknya melalui shouldComponentUpdate:

class CounterButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color !== nextProps.color) {
      return true;
    }
    if (this.state.count !== nextState.count) {
      return true;
    }
    return false;
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

Dalam kode ini, shouldComponentUpdate hanya memeriksa jika ada perubahan dalam props.color atau state.count. Jika nilai variabel-variabel tersebut tidak berubah, komponen tidak akan diperbarui. Jika komponen Anda menjadi lebih kompleks, Anda dapat menggunakan pola serupa dengan melakukan “perbandingan dangkal” (shallow comparison) diantara semua nilai di dalam props dan state untuk menentukan apakah komponen harus diperbarui. Pola ini cukup umum sehingga React menyediakan helper khisis untuk menggunakan logika ini - tinggal melakukan inherit dari React.PureComponent. Jadi kode di bawah adalah cara lebih simpel untuk melakukan hal serupa:

class CounterButton extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

Seringkali, Anda dapat menggunakan React.PureComponent alih-alih menuliskan shouldComponentUpdate versi Anda sendiri. React.PureComponent melakukan perbandingan dangkal, jadi Anda tidak dapat menggunakannya apabila props atau state mungkin termutasi dengan cara yang tidak tertangkap oleh perbandingan dangkal.

Hal ini akan menjadi masalah dalam komponen dengan struktur data yang lebih kompleks. Misalnya, Anda ingin komponen ListOfWords me-render list kata yang terpisahkan oleh koma, dengan komponen induk WordAdder yang memungkinkan Anda mengklik tombol untuk menambahkan kata ke dalam list. Kode di bawah tidak akan bekerja secara benar:

class ListOfWords extends React.PureComponent {
  render() {
    return <div>{this.props.words.join(',')}</div>;
  }
}

class WordAdder extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      words: ['marklar']
    };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    // Bagian ini merupakan contoh buruk dan akan menghasilkan bug
    const words = this.state.words;
    words.push('marklar');
    this.setState({words: words});
  }

  render() {
    return (
      <div>
        <button onClick={this.handleClick} />
        <ListOfWords words={this.state.words} />
      </div>
    );
  }
}

Masalahnya adalah PureComponent akan melakukan perbandingan sederhana antara nilai baru dan lama dari this.props.words. Karena kode ini memutasikan senarai words di dalam method handleClick pada WordAdder, perbandingan nilai lama dan baru dari this.props.words akan menghasilkan nilai serupa, meskipun kata-kata di dalam senarai telah berubah. Oleh karena itu, komponen ListOfWords tidak akan diperbarui meskipun ada kata-kata baru yang seharusnya di-render.

Keuntungan Tidak Memutasi Data

Cara paling simpel untuk menghindari masalah di atas adalah menghindari memutasi nilai-nilai yang akan digunakan di dalam props atau state. Sebagai contoh, method handleClick dapat ditulis ulang dengan concat seperti contoh berikut:

handleClick() {
  this.setState(state => ({
    words: state.words.concat(['marklar'])
  }));
}

ES6 mendukung sintaksis spread untuk senarai yang membuat proses ini lebih mudah. Jika Anda menggunakan Create React App, sintaksis ini sudah bisa digunakan.

handleClick() {
  this.setState(state => ({
    words: [...state.words, 'marklar'],
  }));
};

Anda juga dapat menuliskan ulang kode yang memutasi objek untuk menghindari mutasi, dengan cara yang sama. Sebagai contoh, umpamakan kita memiliki objek bernama colormap dan kita ingin membuat fungsi yang mengubah colormap.right menjadi 'blue'. Kita dapat menuliskan:

function updateColorMap(colormap) {
  colormap.right = 'blue';
}

Untuk menuliskan fungsi ini tanpa memutasikan objek aslinya, kita dapat menggunakan method Object.assign:

function updateColorMap(colormap) {
  return Object.assign({}, colormap, {right: 'blue'});
}

updateColorMap sekarang mengembalikan objek baru, daripada memutasikan objek yang lama. Object.assign berada di spesifikasi ES6 dan membutuhkan polyfill.

Ada juga proposal JavaScript untuk menambahkan properti spread objek untuk membuat pembaruan objek tanpa mutasi menjadi lebih mudah:

function updateColorMap(colormap) {
  return {...colormap, right: 'blue'};
}

Jika Anda menggunakan Create React App, Object.assign dan sintaksis spread untuk objek sudah tersedia.

Ketika berurusan dengan objek bersarang dalam, memperbaruinya secara immutable dapat menjadi sulit. Jika Anda memiliki masalah ini, cobalah Immer atau immutability-helper. Library ini memungkinkan Anda membuat kode yang lebih mudah dibaca tanpa menanggalkan keuntungan-keuntungan immutability.