Pengembang sudut berutang banyak ke zone.js. Dia, misalnya, membantu mencapai kemudahan yang hampir ajaib dalam bekerja dengan Angular. Bahkan, hampir selalu, ketika Anda hanya perlu mengubah beberapa properti, dan kami mengubahnya tanpa memikirkan apa pun, Angular merender ulang komponen yang sesuai. Akibatnya, apa yang dilihat pengguna selalu berisi informasi terbaru. Ini bagus sekali.
Di sini saya ingin mengeksplorasi beberapa aspek tentang bagaimana penggunaan kompiler Ivy baru (yang muncul di Angular 9) dapat sangat membantu penolakan penggunaan zone.js.

Dengan meninggalkan perpustakaan ini, saya dapat secara signifikan meningkatkan kinerja aplikasi Angular yang berjalan di bawah beban berat. Pada saat yang sama, saya berhasil menerapkan mekanisme yang saya butuhkan menggunakan dekorator TypeScript, yang menyebabkan biaya tambahan yang sangat kecil dari sumber daya sistem.
Harap dicatat bahwa pendekatan untuk mengoptimalkan aplikasi Angular, yang disajikan dalam artikel ini, hanya dimungkinkan karena Angular Ivy dan AOT diaktifkan secara default. Artikel ini ditulis untuk tujuan pendidikan, tidak bertujuan mempromosikan pendekatan yang disajikan di dalamnya untuk pengembangan proyek Angular.
Mengapa Anda perlu menggunakan Angular tanpa zone.js?
Sebelum kita melanjutkan, mari kita ajukan satu pertanyaan penting: "Apakah layak menyingkirkan zone.js, mengingat perpustakaan ini membantu kita membuat kembali templat dengan sedikit usaha?" Tentu saja perpustakaan ini sangat bermanfaat. Tetapi, seperti biasa, Anda harus membayar semuanya.
Jika aplikasi Anda memiliki persyaratan kinerja tertentu, menonaktifkan zone.js dapat membantu memenuhi persyaratan itu. Contoh aplikasi di mana kinerja sangat penting adalah proyek yang antarmuka-nya sering diperbarui. Dalam kasus saya, proyek semacam itu ternyata merupakan aplikasi perdagangan waktu-nyata. Bagian kliennya secara konstan menerima pesan melalui protokol WebSocket. Data dari pesan-pesan ini harus ditampilkan secepat mungkin.
Hapus zone.js dari Angular
Sudut dapat dengan mudah dibuat untuk bekerja tanpa zone.js. Untuk melakukan ini, Anda harus terlebih dahulu mengomentari atau menghapus perintah impor yang sesuai, yang terletak di file
polyfills.ts
.
Mengomentari perintah impor zone.jsBerikutnya - Anda perlu melengkapi modul root dengan opsi berikut:
platformBrowserDynamic() .bootstrapModule(AppModule, { ngZone: 'noop' }) .catch(err => console.error(err));
Angular Ivy: Perubahan deteksi otomatis dengan ɵdetectChanges dan ɵmarkDirty
Sebelum kita dapat mulai membuat dekorator TypeScript, kita perlu belajar tentang bagaimana Ivy memungkinkan Anda untuk memohon proses mendeteksi perubahan komponen, membuatnya kotor, dan melewati zone.js dan DI.
Dua fungsi tambahan sekarang tersedia untuk kami, diekspor dari
@angular/core
. Ini adalah
ɵdetectChanges
dan
ɵmarkDirty
. Kedua fungsi ini masih ditujukan untuk penggunaan internal dan tidak stabil - simbol
ɵ
terletak di awal nama mereka.
Mari kita lihat cara menggunakan fitur-fitur ini.
▍ Fungsi ɵmarkDirty
Fungsi ini memungkinkan Anda untuk menandai komponen, membuatnya "kotor," yaitu, perlu dirender ulang. Dia, jika komponen itu tidak ditandai "kotor" sebelum dipanggil, berencana untuk memulai proses deteksi perubahan.
import { ɵmarkDirty as markDirty } from '@angular/core'; @Component({...}) class MyComponent { setTitle(title: string) { this.title = title; markDirty(this); } }
▍ ɵdetectChanges Function
Dokumentasi internal sudut mengatakan bahwa, untuk alasan kinerja, Anda tidak boleh menggunakan
ɵdetectChanges
. Sebagai gantinya, disarankan untuk menggunakan fungsi
ɵmarkDirty
. Fungsi
ɵdetectChanges
serempak memanggil proses mendeteksi perubahan dalam suatu komponen dan subkomponennya.
import { ɵdetectChanges as detectChanges } from '@angular/core'; @Component({...}) class MyComponent { setTitle(title: string) { this.title = title; detectChanges(this); } }
Secara otomatis mendeteksi perubahan menggunakan dekorator TypeScript
Meskipun fungsi-fungsi yang disediakan oleh Angular meningkatkan kegunaan pengembangan dengan membiarkan DI berputar, programmer masih bisa frustrasi oleh kenyataan bahwa ia perlu mengimpor dan memanggil fungsi-fungsi ini sendiri untuk memulai proses deteksi perubahan.
Untuk menyederhanakan dimulainya deteksi perubahan, Anda dapat menulis dekorator TypeScript, yang secara mandiri akan memecahkan masalah ini. Tentu saja, ada beberapa batasan di sini, yang akan kita bahas di bawah ini, tetapi dalam kasus saya pendekatan ini ternyata tepat seperti yang saya butuhkan.
▍ Memperkenalkan dekorator yang diobservasi @
Untuk mendeteksi perubahan, berusaha sesedikit mungkin, kami akan membuat dekorator yang dapat diterapkan dalam tiga cara. Yaitu, itu berlaku untuk entitas berikut:
- Untuk metode sinkron.
- Benda yang bisa diamati.
- Untuk benda biasa.
Pertimbangkan beberapa contoh kecil. Dalam fragmen kode berikut, kami menerapkan dekorator
@observed
ke objek
state
dan ke metode
changeTitle
:
export class Component { title = ''; @observed() state = { name: '' }; @observed() changeTitle(title: string) { this.title = title; } changeName(name: string) { this.state.name = name; } }
- Untuk memeriksa perubahan pada objek
state
, kami menggunakan objek proxy yang memotong perubahan pada objek dan memanggil prosedur untuk mendeteksi perubahan. - Kami mengganti metode
changeTitle
dengan menerapkan fungsi yang pertama kali memanggil metode ini dan kemudian memulai proses deteksi perubahan.
Dan berikut ini adalah contoh dengan
BehaviorSubject
:
export class AppComponent { @observed() show$ = new BehaviorSubject(true); toggle() { this.show$.next(!this.show$.value); } }
Dalam hal objek yang Dapat Diobservasi, menggunakan dekorator terlihat sedikit lebih rumit. Yaitu, Anda perlu berlangganan objek yang diamati dan menandai komponen sebagai "kotor" dalam langganan, tetapi Anda juga harus menghapus langganan. Untuk melakukan ini, kami menetapkan kembali
ngOnInit
dan
ngOnDestroy
untuk berlangganan dan membersihkannya nanti.
▍Membuat dekorator
Berikut adalah tanda tangan dekorator yang
observed
:
export function observed() { return function( target: object, propertyKey: string, descriptor?: PropertyDescriptor ) {} }
Seperti yang Anda lihat,
descriptor
adalah parameter opsional. Ini karena kita membutuhkan dekorator untuk diterapkan pada metode dan properti. Jika parameter ada, ini berarti bahwa dekorator diterapkan ke metode. Dalam hal ini, kami melakukan ini:
Selanjutnya, Anda perlu memeriksa jenis properti apa yang kami hadapi. Ini bisa menjadi objek yang bisa diamati atau objek biasa. Di sini kita akan menggunakan API Angular internal lain. Saya percaya, ini tidak dimaksudkan untuk digunakan dalam aplikasi reguler (maaf!).
Kita berbicara tentang properti
ɵcmp
, yang memberikan akses ke properti yang diproses oleh Angular setelah didefinisikan. Kita dapat menggunakannya untuk mengganti metode
onDestroy
onInit
dan
onDestroy
.
const getCmp = type => (type).ɵcmp; const cmp = getCmp(target.constructor); const onInit = cmp.onInit || noop; const onDestroy = cmp.onDestroy || noop;
Untuk menandai properti sebagai yang akan dimonitor, kami menggunakan
ReflectMetadata
dan menetapkan nilainya menjadi
true
. Akibatnya, kita akan tahu bahwa kita perlu mengamati properti ketika komponen diinisialisasi:
Reflect.set(target, propertyKey, true);
Sekarang saatnya untuk mengganti pengait
onInit
dan memeriksa properti ketika membuat instance komponen:
cmp.onInit = function() { checkComponentProperties(this); onInit.call(this); };
Kami mendefinisikan fungsi
checkComponentProperties
, yang akan mem-bypass properti komponen, memfilternya sesuai dengan nilai yang ditetapkan sebelumnya menggunakan
Reflect.set
:
const checkComponentProperties = (ctx) => { const props = Object.getOwnPropertyNames(ctx); props.map((prop) => { return Reflect.get(target, prop); }).filter(Boolean).forEach(() => { checkProperty.call(ctx, propertyKey); }); };
Fungsi
checkProperty
akan bertanggung jawab untuk mendekorasi properti individual. Pertama, kami memeriksa apakah properti itu objek yang Dapat Diamati atau biasa. Jika ini adalah objek yang dapat Diobservasi, kami berlangganan dan menambahkan langganan ke daftar langganan yang disimpan dalam komponen untuk kebutuhan internalnya.
const checkProperty = function(name: string) { const ctx = this; if (ctx[name] instanceof Observable) { const subscriptions = getSubscriptions(ctx); subscriptions.add(ctx[name].subscribe(() => { markDirty(ctx); })); } else {
Jika properti adalah objek biasa, maka kami akan mengonversinya menjadi objek Proxy dan memanggil
markDirty
dalam fungsi
handler
:
const handler = { set(obj, prop, value) { obj[prop] = value; ɵmarkDirty(ctx); return true; } }; ctx[name] = new Proxy(ctx, handler);
Terakhir, Anda perlu menghapus langganan setelah menghancurkan komponen:
cmp.onDestroy = function() { const ctx = this; if (ctx[subscriptionsSymbol]) { ctx[subscriptionsSymbol].unsubscribe(); } onDestroy.call(ctx); };
Kemungkinan dekorator ini tidak dapat disebut komprehensif. Mereka tidak mencakup semua kemungkinan penggunaan yang mungkin muncul dalam aplikasi besar. Misalnya, ini adalah panggilan ke fungsi templat yang mengembalikan objek yang dapat diobservasi. Tapi saya sedang mengusahakannya.
Meskipun demikian, dekorator di atas sudah cukup untuk proyek kecil saya. Anda akan menemukan kode lengkapnya di akhir materi.
Analisis hasil percepatan aplikasi
Sekarang kita telah berbicara sedikit tentang mekanisme internal Ivy, dan bagaimana membuat dekorator menggunakan mekanisme ini, saatnya untuk menguji apa yang kita dapatkan dalam aplikasi nyata.
Saya, untuk mengetahui pengaruh menyingkirkan zone.js pada kinerja aplikasi Angular, menggunakan proyek hobi
Cryptofolio saya.
Saya menerapkan dekorator ke semua tautan yang diperlukan yang digunakan dalam templat dan dinonaktifkan zone.js. Sebagai contoh, pertimbangkan komponen berikut:
@Component({...}) export class AssetPricerComponent { @observed() price$: Observable<string>; @observed() trend$: Observable<Trend>;
Dua variabel digunakan dalam templat:
price
(harga aset akan ditempatkan di sini) dan
trend
(variabel ini dapat mengambil nilai
up
,
stale
dan
down
, menunjukkan arah perubahan harga). Saya menghiasnya dengan
@observed
.
▍ Ukuran bundel proyek
Untuk memulainya, mari kita lihat seberapa besar ukuran bundel proyek telah berkurang sementara menyingkirkan zone.js. Di bawah ini adalah hasil dari membangun proyek dengan zone.js.
Hasil membangun proyek dengan zone.jsDan di sini adalah perakitan tanpa zone.js.
Hasil membangun proyek tanpa zone.jsPerhatikan
polyfills-es2015.xxx.js
. Jika proyek menggunakan zone.js, maka ukurannya sekitar 35 Kb. Tetapi tanpa zone.js - hanya 130 byte.
OotingBooting
Saya meneliti dua opsi aplikasi menggunakan Mercusuar. Hasil penelitian ini diberikan di bawah ini. Perlu dicatat bahwa saya tidak akan menganggapnya terlalu serius. Faktanya adalah ketika mencoba mencari nilai rata-rata, saya mendapat hasil yang sangat berbeda dengan melakukan beberapa pengukuran untuk versi aplikasi yang sama.
Mungkin perbedaan dalam mengevaluasi dua opsi aplikasi hanya tergantung pada ukuran bundel.
Jadi, inilah hasil yang diperoleh untuk aplikasi yang menggunakan zone.js.
Hasil analisis untuk aplikasi yang menggunakan zone.jsDan inilah yang terjadi setelah menganalisis aplikasi di mana zone.js tidak digunakan.
Hasil analisis untuk aplikasi yang tidak menggunakan zone.js▍ Kinerja
Dan sekarang kita sampai pada yang paling menarik. Ini adalah kinerja aplikasi yang berjalan di bawah beban. Kami ingin mempelajari tentang bagaimana perasaan prosesor ketika aplikasi menampilkan pembaruan harga untuk ratusan aset beberapa kali per detik.
Untuk memuat aplikasi, saya membuat 100 entitas yang menyediakan data bersyarat dengan harga yang berubah setiap 250 ms. Jika harga naik, itu ditampilkan dalam warna hijau. Jika dikurangi - merah. Semua ini dengan serius dapat memuat MacBook Pro saya.
Perlu dicatat bahwa ketika bekerja di sektor keuangan pada beberapa aplikasi yang dirancang untuk transmisi fragmen data frekuensi tinggi, saya telah menemukan situasi yang sama beberapa kali.
Untuk menganalisis perbedaan versi aplikasi menggunakan sumber daya prosesor, saya menggunakan alat pengembang Chrome.
Seperti apa tampilan aplikasi yang menggunakan zone.js.
Pemuatan sistem dibuat oleh aplikasi yang menggunakan zone.jsDan inilah cara aplikasi bekerja di mana zone.js tidak digunakan.
Pemuatan sistem dibuat oleh aplikasi yang tidak menggunakan zone.jsKami menganalisis hasil ini, memperhatikan grafik beban prosesor (kuning):
- Seperti yang Anda lihat, aplikasi yang menggunakan zone.js terus-menerus memuat prosesor hingga 70-100%! Jika Anda menahan tab browser terbuka untuk waktu yang lama, membuat beban seperti itu pada sistem, maka aplikasi yang berjalan di dalamnya mungkin gagal.
- Dan versi aplikasi di mana zone.js tidak digunakan menciptakan beban yang stabil pada prosesor dalam kisaran 30 hingga 40%. Hebat!
Harap perhatikan bahwa hasil ini diperoleh dengan jendela Alat Pengembang Chrome terbuka, yang juga membuat sistem tegang dan memperlambat aplikasi.
▍ peningkatan beban
Saya mencoba memastikan bahwa setiap entitas yang bertanggung jawab untuk memperbarui harga akan mengeluarkan 4 pembaruan lagi setiap detik selain apa yang sudah dihasilkannya.
Inilah yang berhasil kami ketahui tentang aplikasi di mana zone.js tidak digunakan:
- Aplikasi ini biasanya diatasi dengan beban, sekarang menggunakan sekitar 50% dari sumber daya prosesor.
- Dia berhasil memuat prosesor sebanyak aplikasi dengan zone.js, hanya ketika harga diperbarui setiap 10 ms (data baru, seperti sebelumnya, berasal dari 100 entitas).
▍ Analisis Kinerja dengan Benchpress Sudut
Analisis kinerja yang saya lakukan di atas tidak dapat disebut sangat ilmiah. Untuk studi yang lebih serius tentang kinerja berbagai kerangka kerja, saya akan merekomendasikan menggunakan
tolok ukur ini . Untuk penelitian, Angular harus memilih versi biasa dari kerangka ini dan versinya tanpa zone.js.
Saya, terinspirasi oleh beberapa ide tolok ukur ini, menciptakan sebuah
proyek yang melakukan perhitungan berat. Saya menguji kinerjanya dengan
Angular Benchpress .
Berikut adalah kode komponen yang diuji:
@Component({...}) export class AppComponent { public data = []; @observed() run(length: number) { this.clear(); this.buildData(length); } @observed() append(length: number) { this.buildData(length); } @observed() removeAll() { this.clear(); } @observed() remove(item) { for (let i = 0, l = this.data.length; i < l; i++) { if (this.data[i].id === item.id) { this.data.splice(i, 1); break; } } } trackById(item) { return item.id; } private clear() { this.data = []; } private buildData(length: number) { const start = this.data.length; const end = start + length; for (let n = start; n <= end; n++) { this.data.push({ id: n, label: Math.random() }); } } }
Saya meluncurkan satu set tolok ukur kecil menggunakan Protractor dan Benchpress. Operasi dilakukan beberapa kali.
Benchpress beraksiHasil
Berikut adalah contoh hasil yang diperoleh dengan menggunakan Benchpress.
Hasil BenchpressBerikut ini penjelasan indikator yang disajikan dalam tabel ini:
gcAmount
: volume operasi gc (pengumpulan sampah), Kb.gcTime
: waktu operasi gc, ms.majorGcTime
: waktu operasi utama gc, ms.pureScriptTime
: waktu eksekusi skrip dalam ms, tidak termasuk operasi gc dan rendering.renderTime
: waktu render, ms.scriptTime
: waktu pelaksanaan skrip dengan mempertimbangkan operasi dan rendering gc akun.
Sekarang kita akan mempertimbangkan analisis kinerja beberapa operasi di berbagai varian aplikasi. Hijau menunjukkan hasil aplikasi yang menggunakan zone.js, oranye menunjukkan hasil aplikasi tanpa zone.js. Harap dicatat bahwa hanya waktu rendering dianalisis di sini. Jika Anda tertarik dengan semua hasil tes, periksa di
sini .
Uji: membuat 1000 baris
Dalam tes pertama, 1000 baris dibuat.
Hasil tesUji: membuat 10.000 baris
Seiring bertambahnya beban pada aplikasi, begitu pula perbedaan dalam kinerjanya.
Hasil tesUji: gabung 1000 baris
Dalam tes ini, 1000 baris ditambahkan ke 10.000 baris.
Hasil tesUji: menghapus 10.000 baris
Di sini, 10.000 baris dibuat, yang kemudian dihapus.
Hasil tesKode Sumber Dekorator TypeScript
Di bawah ini adalah kode sumber dekorator TypeScript yang dibahas di sini. Kode ini juga dapat ditemukan di
sini .
Ringkasan
Meskipun saya berharap Anda menyukai cerita saya tentang mengoptimalkan kinerja proyek Angular, saya juga berharap bahwa saya tidak akan membuat Anda terburu-buru untuk menghapus zone.js dari proyek Anda. Strategi yang dijelaskan di sini harus menjadi pilihan terakhir yang dapat Anda gunakan untuk meningkatkan kinerja aplikasi Angular Anda.
Pertama, Anda perlu mencoba pendekatan seperti menggunakan strategi deteksi perubahan OnPush, menerapkan
trackBy
, menonaktifkan komponen, mengeksekusi kode di luar zone.js, peristiwa blacklisting zone.js (daftar metode optimasi ini dapat dilanjutkan). Pendekatan yang diperlihatkan di sini cukup mahal, dan saya tidak yakin semua orang mau membayar dengan harga tinggi untuk kinerja.
Bahkan, pengembangan tanpa zone.js mungkin bukan hal yang paling menarik. Mungkin ini tidak hanya untuk mereka yang terlibat dalam proyek di bawah kendali penuhnya. Artinya - itu adalah pemilik dependensi dan memiliki kemampuan dan waktu untuk membawa semuanya ke bentuk yang semestinya.
Jika ternyata Anda telah mencoba segalanya dan percaya bahwa hambatan proyek Anda adalah zone.js, maka mungkin Anda harus mencoba mempercepat Angular dengan mendeteksi perubahan secara independen.
Saya harap artikel ini memungkinkan Anda untuk melihat apa yang diharapkan Angular di masa depan, apa yang mampu dilakukan Ivy, dan apa yang dapat dilakukan dengan zone.js untuk memaksimalkan kecepatan aplikasi.
Pembaca yang budiman! Bagaimana Anda mengoptimalkan proyek Sudut Anda yang membutuhkan kinerja maksimal?
