Harga tersembunyi perpustakaan CSS-in-JS di Bereaksi aplikasi

Dalam aplikasi front-end modern, teknologi CSS-in-JS cukup populer. Masalahnya adalah memberi pengembang mekanisme untuk bekerja dengan gaya yang lebih nyaman daripada CSS biasa. Jangan salah sangka. Saya sangat suka CSS, tetapi membuat arsitektur CSS yang baik bukanlah tugas yang mudah. Teknologi CSS-in-JS menawarkan beberapa keunggulan signifikan dibandingkan gaya CSS konvensional. Namun, sayangnya, penggunaan CSS-in-JS dapat, dalam aplikasi tertentu, menyebabkan masalah kinerja. Pada artikel ini saya akan mencoba untuk menguraikan fitur tingkat tinggi dari perpustakaan CSS-in-JS paling populer, berbicara tentang beberapa masalah yang kadang muncul ketika menggunakannya, dan menyarankan cara untuk mengurangi masalah ini.



Tinjauan situasi


Di perusahaan saya, diputuskan untuk membuat perpustakaan UI. Ini akan membawa kita banyak manfaat, akan memungkinkan kita untuk menggunakan kembali fragmen antarmuka standar dalam berbagai proyek. Saya adalah salah satu sukarelawan yang menerima tugas itu. Saya memutuskan untuk menggunakan teknologi CSS-in-JS, karena saya sudah terbiasa dengan styling API dari perpustakaan CSS-in-JS paling populer. Dalam perjalanan kerja, saya berusaha untuk bertindak secara wajar. Saya merancang logika yang dapat digunakan kembali dan menerapkan properti bersama dalam komponen. Karena itu, saya mengambil komposisi komponen. Sebagai contoh, komponen <IconButton /> memperluas komponen <BaseButton /> , yang, pada gilirannya, merupakan implementasi dari entitas styled.button sederhana. Sayangnya, ternyata IconButton membutuhkan gaya sendiri, jadi saya mengubah komponen ini menjadi komponen bergaya:

 const IconButton = styled(BaseButton)`  border-radius: 3px; `; 

Semakin banyak komponen muncul di perpustakaan kami, kami menggunakan semakin banyak operasi komposisi. Ini sepertinya tidak wajar. Bagaimanapun, komposisi sebenarnya adalah dasar dari React. Semuanya baik-baik saja sampai saya membuat komponen Table . Saya mulai merasa bahwa komponen ini berubah dengan lambat. Terutama dalam situasi di mana jumlah baris dalam tabel melebihi 50. Ini tidak benar. Oleh karena itu, saya mulai memahami masalahnya dengan menggunakan alat pengembang.

Omong-omong, jika Anda pernah bertanya-tanya mengapa aturan CSS tidak dapat diedit menggunakan inspektur alat pengembang, perlu diketahui bahwa ini karena mereka menggunakan CSSStyleSheet.insertRule () . Ini adalah cara yang sangat cepat untuk memodifikasi style sheet. Tetapi salah satu kelemahannya adalah kenyataan bahwa style sheet yang sesuai tidak lagi dapat diedit oleh inspektur.

Tak perlu dikatakan, pohon yang dihasilkan oleh React sangat besar. Jumlah komponen Context.Consumer begitu besar sehingga saya bisa tidur. Faktanya adalah bahwa setiap kali komponen gaya tunggal dirender menggunakan komponen-gaya atau emosi , selain menciptakan komponen Bereaksi reguler, Context.Consumer tambahan. Komponen Context.Consumer juga Context.Consumer . Ini diperlukan untuk memungkinkan skrip yang sesuai (kebanyakan pustaka CSS-in-JS bergantung pada skrip yang dieksekusi saat halaman sedang berjalan) untuk memproses dengan benar aturan gaya yang dihasilkan. Biasanya ini tidak menimbulkan masalah khusus, tetapi kita tidak boleh lupa bahwa komponen harus memiliki akses ke topik. Ini berarti perlunya membuat Context.Consumer tambahan. Context.Consumer untuk setiap elemen bergaya, yang memungkinkan Anda untuk "membaca" tema dari komponen ThemeProvider . Akibatnya, saat membuat komponen bergaya dalam aplikasi dengan tema, 3 komponen dibuat. Ini adalah satu komponen StyledXXX dan dua komponen Context.Consumer lainnya.

Benar, tidak ada yang mengerikan di sini, karena React melakukan tugasnya dengan cepat, yang berarti bahwa dalam kebanyakan kasus kita tidak perlu khawatir. Tetapi bagaimana jika beberapa komponen bergaya disusun untuk menciptakan komponen yang lebih kompleks? Bagaimana jika komponen kompleks ini adalah bagian dari daftar panjang atau tabel besar di mana setidaknya 100 komponen ini diberikan? Di sini dalam situasi seperti itu, kita dihadapkan dengan masalah ...

Pembuatan profil


Untuk menguji berbagai solusi CSS-in-JS, saya membuat aplikasi sederhana. Ini menampilkan teks Hello World 50 kali. Dalam versi pertama aplikasi ini, saya membungkus teks ini dalam elemen div reguler. Dalam yang kedua , saya menggunakan komponen styled.div . Selain itu, saya menambahkan tombol ke aplikasi yang menyebabkan semua 50 elemen ini di-render ulang.

Setelah merender komponen <App /> , dua React tree berbeda ditampilkan. Gambar-gambar berikut menunjukkan elemen pohon yang disimpulkan oleh Bereaksi.


Pohon ditampilkan dalam aplikasi yang menggunakan elemen div reguler


Sebuah pohon ditampilkan dalam aplikasi yang menggunakan styled.div

Kemudian, menggunakan tombol, saya membuat <App /> 10 kali untuk mengumpulkan data mengenai beban pada sistem, yang komponen tambahan dari Context.Consumer . Berikut informasi tentang pembuatan ulang aplikasi berulang kali dengan elemen div reguler dalam mode desain.


Merender ulang aplikasi dengan elemen div reguler dalam mode desain. Nilai rata-rata adalah 2,54 ms.


Merender ulang aplikasi dengan elemen styled.div dalam mode pengembangan. Nilai rata-rata adalah 3,98 ms.

Yang sangat menarik adalah bahwa, rata-rata, aplikasi CSS-in-JS lebih lambat 56,6% dari biasanya. Tapi itu adalah mode pengembangan. Bagaimana dengan mode produksi?


Merender ulang aplikasi dengan elemen div yang biasa dalam mode produksi. Nilai rata-rata adalah 1,06 ms.


Merender ulang aplikasi dengan elemen styled.div dalam mode produksi. Nilai rata-rata adalah 2,27 ms.

Ketika mode produksi dihidupkan, implementasi div aplikasi tampaknya lebih dari 50% lebih cepat dibandingkan dengan versi yang sama dalam mode pengembangan. Aplikasi styled.div hanya 43% lebih cepat. Dan di sini, seperti sebelumnya, jelas bahwa solusi CSS-in-JS hampir dua kali lebih lambat dari solusi biasa. Apa yang memperlambatnya?

Analisis aplikasi selama pelaksanaannya


Jawaban yang jelas untuk pertanyaan apa yang memperlambat aplikasi CSS-in-JS adalah sebagai berikut: "Dikatakan bahwa seratus perpustakaan CSS-in-JS membuat dua Context.Consumer . Context.Consumer per komponen." Tetapi jika Anda memikirkan semua ini Context.Consumer , maka Context.Consumer hanyalah mekanisme untuk mengakses variabel JS. Tentu saja, Bereaksi perlu melakukan beberapa pekerjaan untuk mencari tahu di mana membaca nilai yang sesuai, tetapi ini saja tidak menjelaskan hasil pengukuran di atas. Jawaban sebenarnya untuk pertanyaan ini dapat ditemukan dengan menganalisis alasan menggunakan Context.Consumer . Faktanya adalah bahwa sebagian besar pustaka CSS-in-JS mengandalkan skrip yang berjalan selama output halaman di browser, yang membantu pustaka secara dinamis memperbarui gaya komponen. Perpustakaan ini tidak membuat kelas CSS selama perakitan halaman. Sebaliknya, mereka secara dinamis menghasilkan dan memperbarui <style> dalam dokumen. Ini dilakukan ketika komponen dipasang, atau ketika sifat-sifatnya berubah. Tag ini biasanya berisi kelas CSS tunggal yang nama hashnya dipetakan ke komponen Bereaksi tunggal. Saat properti komponen ini berubah, tag <style> sesuai juga harus berubah. Berikut cara menjelaskan apa yang sedang dilakukan selama proses ini:

  • Aturan CSS yang harus dimiliki tag <style> dibuat ulang.
  • Nama kelas hash baru dibuat yang digunakan untuk menyimpan aturan CSS yang disebutkan sebelumnya.
  • Properti classname dari komponen Bereaksi yang sesuai diperbarui ke yang baru, menunjukkan kelas yang baru saja dibuat.

Pertimbangkan, misalnya, perpustakaan styled-components . Saat membuat komponen styled.div perpustakaan memberikan pengenal internal (ID) ke komponen ini dan menambahkan tag <style> ke tag HTML <head> . Tag ini berisi satu komentar yang merujuk ke pengenal internal komponen Bereaksi yang menjadi milik gaya yang sesuai:

 <style data-styled-components>   /* sc-component-id: sc-bdVaJa */ </style> 

Dan berikut ini adalah tindakan yang dilakukan oleh pustaka komponen-gaya saat menampilkan komponen Bereaksi yang sesuai:

  1. Parsing aturan CSS dari string template komponen-gaya.
  2. Menghasilkan nama kelas CSS baru (atau mencari tahu apakah akan menyimpan nama sebelumnya).
  3. Melakukan preprocessing gaya menggunakan stylis.
  4. Sematkan CSS yang dihasilkan dari preprocessing ke tag <style> sesuai dari tag <head> HTML.

Agar dapat menggunakan topik di langkah # 1 dari proses ini, Context.Consumer diperlukan . Berkat komponen ini, nilainya dibaca dari topik di string templat. Agar dapat memodifikasi tag <style> terkait dengan komponen ini, satu lagi Context.Consumer diperlukan dari komponen. Ini memungkinkan Anda untuk mengakses instance stylesheet. Itulah sebabnya di sebagian besar pustaka CSS-in-JS kami menemukan dua contoh Context.Consumer .

Selain itu, karena semua perhitungan ini mempengaruhi antarmuka pengguna, harus dicatat bahwa mereka harus dilakukan selama fase rendering komponen. Mereka tidak dapat dieksekusi dalam kode penangan acara untuk siklus hidup komponen Bereaksi (cara ini eksekusi mereka dapat ditunda dan akan terlihat seperti pembentukan halaman lambat untuk pengguna). Itu sebabnya rendering aplikasi styled.div lebih lambat daripada rendering aplikasi biasa.

Semua ini telah diperhatikan oleh pengembang komponen yang ditata. Mereka mengoptimalkan perpustakaan untuk mengurangi waktu rendering komponen. Secara khusus, perpustakaan mengetahui apakah komponen bergaya "statis". Yaitu, apakah gaya komponen bergantung pada tema atau pada properti yang diteruskan ke sana. Sebagai contoh, komponen statis ditunjukkan di bawah ini:

 const StaticStyledDiv = styled.div`  color:red `; 

Dan komponen ini tidak statis:

 const DynamicStyledDiv = styled.div`  color: ${props => props.color} `; 

Jika pustaka menemukan bahwa komponen itu statis, ia lompati 4 langkah di atas, menyadari bahwa itu tidak akan pernah harus mengubah nama kelas yang dihasilkan (karena tidak ada elemen dinamis yang mungkin diperlukan untuk mengubah aturan CSS yang terkait dengannya). Selain itu, dalam situasi ini, pustaka tidak menampilkan ThemeContext.Consumer sekitar komponen bergaya, karena ketergantungan tema tidak akan lagi membiarkan komponen dianggap "statis".

Jika Anda cukup berhati-hati ketika menganalisis tangkapan layar yang disajikan sebelumnya, maka Anda mungkin memperhatikan bahwa bahkan dalam mode produksi untuk setiap styled.div Dua komponen. Komponen pelanggan Context.Consumer . Cukup menarik, komponen yang dirender adalah "statis," karena tidak ada aturan CSS dinamis yang dikaitkan dengannya. Dalam situasi seperti itu, orang akan berharap bahwa jika contoh ini ditulis menggunakan pustaka komponen-gaya, itu tidak akan Context.Consumer perlu bekerja dengan tema. Alasan mengapa tepat dua Context.Consumer ditampilkan di sini adalah karena percobaan, data yang diberikan di atas, dilakukan dengan menggunakan emosi - perpustakaan CSS-in-JS lainnya. Pustaka ini mengambil pendekatan yang hampir sama dengan komponen yang ditata. Perbedaan di antara mereka kecil. Jadi, perpustakaan emosi mem-parsing string templat, memproses ulang gaya menggunakan stylis, dan memperbarui konten dari <style> sesuai. Namun, di sini, satu perbedaan utama harus diperhatikan antara komponen yang ditata dan emosi. Terdiri dari fakta bahwa pustaka emosi selalu membungkus semua komponen di ThemeContext.Consumer - terlepas dari apakah mereka menggunakan tema atau tidak (ini menjelaskan tampilan tangkapan layar di atas). Cukup menarik, meskipun emosi membuat lebih banyak komponen Konsumen daripada komponen yang ditata, emosi mengungguli komponen yang ditata dalam hal kinerja. Ini menunjukkan bahwa jumlah komponen Context.Consumer bukan faktor utama dalam memperlambat rendering. Perlu dicatat bahwa pada saat penulisan materi ini, versi beta komponen-gaya v5.xx dirilis, yang, menurut pengembang perpustakaan, mem-bypass emosi dalam hal kinerja.

Ringkaslah apa yang kita bicarakan. Ternyata kombinasi dari banyak elemen Context.Consumer (yang berarti bahwa React harus mengoordinasikan pekerjaan elemen tambahan) dan mekanisme styling dinamis internal dapat memperlambat aplikasi. Saya harus mengatakan bahwa semua tag <style> ditambahkan ke <head> untuk setiap komponen tidak pernah dihapus. Ini disebabkan oleh fakta bahwa operasi penghapusan elemen membuat beban besar pada DOM (misalnya, browser harus mengatur ulang halaman karena hal ini). Muatan ini lebih tinggi daripada beban tambahan pada sistem yang disebabkan oleh kehadiran pada halaman elemen <style> tidak perlu. Sejujurnya, saya tidak bisa mengatakan dengan yakin bahwa tag <style> yang tidak perlu dapat menyebabkan masalah kinerja. Mereka hanya menyimpan kelas yang tidak digunakan yang dihasilkan selama operasi halaman (yaitu, data ini tidak dikirim melalui jaringan). Tetapi Anda harus tahu tentang fitur ini menggunakan teknologi CSS-in-JS.

Saya harus mengatakan bahwa tag <style> tidak membuat semua perpustakaan CSS-in-JS, karena tidak semuanya didasarkan pada mekanisme yang beroperasi selama pengoperasian halaman di browser. Misalnya, pustaka linaria tidak melakukan apa-apa saat halaman berjalan di browser.

Ini mendefinisikan satu set kelas CSS tetap selama proses pembangunan proyek dan menyesuaikan korespondensi semua aturan dinamis dalam string template (yaitu, aturan CSS yang bergantung pada properti komponen) dengan properti CSS kustom. Akibatnya, ketika properti komponen berubah, properti CSS berubah dan tampilan antarmuka berubah. Berkat ini, linaria jauh lebih cepat daripada perpustakaan yang mengandalkan mekanisme yang beroperasi saat halaman berjalan. Masalahnya adalah bahwa ketika menggunakan perpustakaan ini, sistem harus melakukan perhitungan jauh lebih sedikit selama rendering komponen. Satu-satunya hal yang perlu Anda lakukan ketika menggunakan linaria selama rendering adalah ingat untuk memperbarui properti CSS kustom. Namun, pada saat yang sama, pendekatan ini tidak kompatibel dengan IE11, ia memiliki dukungan terbatas untuk properti CSS populer dan, tanpa konfigurasi tambahan, tidak memungkinkan Anda untuk menggunakan tema. Seperti halnya dengan bidang pengembangan web lainnya, di antara perpustakaan CSS-in-JS-tidak ada yang ideal, cocok untuk semua kesempatan.

Ringkasan


Teknologi CSS-in-JS pada suatu waktu tampak seperti revolusi di bidang penataan. Itu membuat hidup lebih mudah bagi banyak pengembang, dan juga memungkinkan, tanpa upaya tambahan, untuk memecahkan banyak masalah, seperti tabrakan nama dan penggunaan awalan pabrikan browser. Artikel ini ditulis untuk menjelaskan pertanyaan tentang seberapa populer perpustakaan CSS-in-JS (yang mengontrol gaya saat halaman berjalan) dapat mempengaruhi kinerja proyek web. Saya ingin menarik perhatian khusus pada fakta bahwa pengaruh perpustakaan ini pada kinerja tidak selalu mengarah pada munculnya masalah yang nyata. Bahkan, di sebagian besar aplikasi efek ini sama sekali tidak terlihat. Masalah dapat terjadi pada aplikasi yang memiliki halaman yang berisi ratusan komponen kompleks.

Manfaat CSS-in-JS biasanya lebih besar daripada potensi efek negatif dari penggunaan teknologi ini. Namun, kelemahan CSS-in-JS harus diingat oleh pengembang yang aplikasinya membuat data dalam jumlah besar, yang proyeknya mengandung banyak elemen antarmuka yang terus berubah. Jika Anda mencurigai bahwa aplikasi Anda terkena efek negatif CSS-in-JS, maka sebelum melakukan refactoring, ada baiknya semuanya dievaluasi dan diukur dengan baik.

Berikut adalah beberapa kiat untuk meningkatkan kinerja aplikasi yang menggunakan pustaka CSS-in-JS populer yang melakukan tugasnya ketika halaman berjalan di browser:

  1. Jangan terlalu terbawa oleh komposisi komponen bergaya. Cobalah untuk tidak mengulangi kesalahan yang saya bicarakan di awal, dan jangan mencoba, untuk membuat tombol yang tidak menguntungkan, buatlah komposisi tiga komponen bergaya. Jika Anda ingin "menggunakan kembali" kode, gunakan properti CSS dan buat string templat. Ini akan memungkinkan Anda melakukannya tanpa banyak komponen yang tidak perlu dari Context.Consumer . Akibatnya, React harus mengelola lebih sedikit komponen, yang akan meningkatkan produktivitas proyek.
  2. Berusaha keras untuk menggunakan komponen "statis". Beberapa perpustakaan CSS-in-JS mengoptimalkan kode yang dihasilkan jika gaya komponen tidak bergantung pada tema atau properti. Semakin "statis" ada dalam string template, semakin tinggi kemungkinan skrip dalam pustaka CSS-in-JS akan berjalan lebih cepat.
  3. Cobalah untuk menghindari operasi rendering ulang aplikasi Bereaksi Anda yang tidak perlu. Berjuang untuk membuat hanya ketika Anda benar-benar membutuhkannya. Berkat ini, tindakan Bereaksi atau tindakan CSS-in-JS library tidak akan dimuat. Render ulang adalah operasi yang hanya dapat dilakukan dalam kasus luar biasa. Misalnya - dengan penarikan simultan sejumlah besar komponen "berat".
  4. Cari tahu apakah pustaka CSS-in-JS cocok untuk proyek Anda yang tidak menggunakan skrip yang dijalankan saat halaman berjalan di browser. Terkadang kami memilih teknologi CSS-in-JS karena lebih nyaman bagi pengembang untuk menggunakannya, dan bukan berbagai JavaScript-API. Tetapi jika aplikasi Anda tidak membutuhkan dukungan jika tidak menggunakan properti CSS secara intensif, maka mungkin Anda dapat menggunakan pustaka CSS-in-JS seperti linaria yang tidak menggunakan skrip yang berjalan saat halaman sedang berjalan. Pendekatan ini, di samping itu, akan mengurangi ukuran bundel aplikasi sekitar 12 Kb. Faktanya adalah bahwa ukuran kode dari kebanyakan perpustakaan CSS-in-JS cocok dalam 12-15 Kb, dan kode linaria yang sama kurang dari 1 Kb.

Pembaca yang budiman! Apakah Anda menggunakan perpustakaan CSS-in-JS?


Source: https://habr.com/ru/post/id480358/


All Articles