Halo, Habr! Saya terlibat dalam pengembangan sistem ECM. Dan dalam serangkaian artikel singkat saya ingin berbagi pengalaman dan sejarah pengembangan Kotak Data React saya (selanjutnya hanya kotak), yaitu:
- mengapa kami meninggalkan komponen yang sudah jadi
- masalah dan tugas apa yang kami hadapi ketika mengembangkan jaringan kami
- apa keuntungan yang berkembang dari kisi Anda
Latar belakang
Sistem kami memiliki aplikasi web tempat pengguna bekerja dengan daftar dokumen, hasil pencarian, direktori. Selain itu, daftar dapat berupa kecil (10 karyawan) atau sangat besar (50.000 kontraktor). Untuk menampilkan daftar ini, kami mengembangkan kisi kami sendiri:

Ketika kami mulai mengembangkan aplikasi web, kami ingin menemukan perpustakaan yang sudah jadi untuk menampilkan kisi-kisi yang dapat melakukan semua yang kami butuhkan: mengurutkan dan mengelompokkan catatan, menyeret dan melepas kolom, bekerja dengan banyak pilihan, memfilter dan menghitung total kolom, sebagian Unduh data dari server dan tampilkan puluhan ribu catatan.
Saya akan menjelaskan persyaratan terakhir "menampilkan puluhan ribu catatan." Dalam kisi-kisi, persyaratan ini diimplementasikan dalam beberapa cara: paging, scrolling infinity, scrolling virtual.
Pendekatan paging dan scrolling tanpa batas adalah umum di situs web, Anda menggunakannya setiap hari. Misalnya, paging di Google:

Atau gulir tak terhingga di Google yang sama pada gambar, tempat bagian gambar berikutnya dimuat saat Anda menggulir ke bagian akhir bagian pertama:

Tetapi pengguliran virtual (selanjutnya disebut pengguliran virtual) jarang digunakan di web, perbedaan utamanya dari pengguliran tak terhingga adalah kemampuan untuk dengan cepat menggulir daftar yang sangat besar di mana saja. Dalam hal ini, hanya data yang terlihat oleh pengguna yang akan diunduh dan ditampilkan.

Untuk aplikasi web kami, saya ingin menggunakan scrolling virtual. Saya setuju bahwa menggulir ke tempat mana pun dalam daftar 10.000 entri adalah kasus yang agak diciptakan. Namun, pengguliran acak dalam 500-1000 catatan adalah kasus nyata.
Ketika mereka menerapkan pengguliran virtual, mereka sering mengimplementasikan API perangkat lunak untuk mengelola pengguliran ini. Ini adalah fitur yang sangat penting. Pengguliran perangkat lunak digunakan, misalnya, untuk menempatkan catatan yang dipilih di tengah layar saat membuka direktori:

Kembali ke persyaratan. Apa lagi yang kami butuhkan:
- API Manajemen Pengguliran Virtual
- Kustomisasi tampilan kisi (baris, kolom, menu konteks) sehingga kisi tidak terlihat asing di aplikasi kita
- Dukungan untuk teknologi yang kami gunakan: react, redux, dan flexbox
- Bahwa grid bekerja di ie11
Secara umum, ada banyak persyaratan.
Upaya pertama (2016). Kotak Data JavaScript ExExtreme
Tidak lama menjelajahi perpustakaan yang ada, kami menemukan Grid Data JavaScript DevExtreme. Dengan persyaratan fungsional, kisi-kisi ini memenuhi semua kebutuhan kami dan memiliki penampilan yang sangat rapi. Namun, itu tidak cocok untuk persyaratan teknologi (tidak bereaksi, tidak redux, tidak flexbox). Pada saat itu, DevExtreme tidak memiliki kotak reaksi.
Yah, janganlah bereaksi, kami memutuskan, karena kisi-kisi itu indah dan fungsional, kami akan menggunakannya. Dan mereka menambahkan perpustakaan ke proyek mereka. Ternyata kami menambahkan 3 MB skrip.
Selama beberapa minggu, kami mengintegrasikan kisi-kisi ke dalam aplikasi web kami dan meningkatkan fungsionalitas dasar:
- Dibungkus kotak untuk berteman dengan reaksi dan redux
- Mengangkat pengguliran virtual dan memuat sebagian data dari server web kami
- Penyortiran dan seleksi yang diterapkan
Dalam proses mengacaukan kisi-kisi, dua masalah serius menjadi jelas dan sejumlah masalah yang kurang serius.
Masalah serius pertama
Untuk membuat DevExtreme JavaScript Data Grid dengan redux sangat sulit. Kami berhasil mengontrol pengaturan kolom dan menyorot catatan melalui redux, tetapi untuk menyimpan data yang dimuat porsi dalam redux, dan melakukan operasi CRUD pada mereka melalui redux - ini tidak realistis. Saya harus membuat kruk itu, melewati redux, memanipulasi data grid. Tongkat itu ternyata rumit dan rapuh. Ini adalah bel alarm pertama yang tidak cocok dengan jaringan kami, tetapi kami terus mengacaukannya.
Masalah serius kedua
Tidak ada API manajemen gulir virtual. Kami tidak dapat menolak kontrol perangkat lunak dari pengguliran, kami harus mengulang sumber DevExtreme dan menemukan API kontrol pengguliran internal. Tentu saja, API ini memiliki sejumlah keterbatasan, karena dirancang untuk penggunaan internal. Akibatnya, kami berhasil membuat API internal lebih atau kurang berfungsi pada kasus kami, tetapi sekali lagi, melewati redux, dan lagi sekelompok kruk.
Masalah yang kurang serius
Masalah yang kurang serius muncul setiap saat, karena fungsi standar Grid Data JavaScript DevExtreme tidak sepenuhnya cocok untuk kami, dan kami mencoba memperbaikinya:
- Meregangkan grid DevExtreme di ketinggian tidak bekerja. Saya harus menulis hack untuk mengajar DevExtreme bagaimana melakukan ini (mungkin tidak ada masalah dengan ini di versi terbaru).
- Ketika fokus tidak di grid, maka tidak mungkin untuk mengontrol pemilihan garis melalui keyboard (dan kami membutuhkannya). Saya harus menulis kontrol keyboard saya.
- Saat mengubah komposisi kolom dan mengubah data, kami memiliki masalah berkedip data (dengan pengguliran virtual diaktifkan).
- Masalah sejumlah besar permintaan di tampilan pertama kotak. Itu terutama terlihat ketika kami mengontrol pengguliran melalui API internal.
- Sulit untuk menyesuaikan beberapa bagian dari kisi UI. Misalnya, ada keinginan di atas garis kisi yang dipilih untuk menggambar tindakan manajemen garis (hapus garis, salin, buka kartu). Tetapi bagaimana mengacaukan ini ke DevExtreme tidak jelas, dan bahkan menggunakan react:

- Sulit untuk menyortir penyortiran (kami ingin menyortir berdasarkan data yang tidak ditampilkan dalam kisi dan tidak dipetakan ke dalam kolom).
- Kruk diperlukan untuk mengacaukan komponen reaksi ke dalam sel-sel kisi (setelah semua, kisi tidak pada reaksi).
- Tidak mengetik kode DevExtreme (aliran / naskah).
- Masalah kecepatan dengan pengguliran virtual yang panjang.
- Masalah kecepatan saat meregangkan / menyusun ulang kolom (setelah pengguliran virtual yang berkepanjangan).
- Ukuran skrip kotak adalah 3 Mb.
Meskipun grid fungsionalitas DevExtreme berisi semua yang kami butuhkan, tetapi saya ingin menulis ulang hampir semua fungsionalitas standar. Selama penggunaannya, ditambahkan ratusan baris kode yang sulit dipahami yang mencoba memecahkan masalah berinteraksi dengan redux dan bereaksi, sulit untuk menggunakan grid non-reaksi dalam aplikasi reaksi.
Penolakan DevExtreme. Cari alternatif
Setelah beberapa waktu menggunakan DevExtreme, diputuskan untuk meninggalkannya. Buang semua peretasan, kode kompleks, serta skrip DevExtreme 3 MB. Dan temukan atau tulis kisi baru.
Kali ini, kami lebih memperhatikan studi grid yang ada. MS Fabric DetailsList, ReactVirtualized Grid, DevExtreme React Grid, Telerik Grid, KendoUI Grid dipelajari.
Persyaratannya tetap sama, tetapi sudah ada dalam daftar yang jelas bagi kami.
Persyaratan Teknologi:
Persyaratan Fungsional:
- Pengguliran virtual (dengan kemampuan menampilkan puluhan ribu catatan)
- API Manajemen Bergulir
- Menyimpan data dan pengaturan kisi dalam redux
- Batch memuat data dari server web
- Manajemen kolom (regangkan / atur ulang / kontrol visibilitas)
- Pemilahan kolom + pemfilteran
- Pilihan ganda
- Suka-cari dengan backlight
- Pengguliran horizontal
- Keyboard
- Menu konteks (pada garis, pada area kosong, pada kolom)
- Mendukung ie11, edge, chrome, ff, safari
Pada titik ini, versi pertama dari DevExtreme React Grid sudah muncul, tetapi kami segera menjatuhkannya karena alasan berikut:
- Pengguliran virtual tidak didukung di ie11
- Pengguliran virtual tidak berfungsi bersamaan dengan pengunduhan data batch dari server (meskipun tampaknya ada beberapa solusi).
- Dan yang paling penting, saya tidak ingin menginjak penggaruk yang sama ketika saya ingin menulis ulang setengah fungsi standar dari kisi pihak ketiga.
Analisis solusi yang ada menunjukkan bahwa tidak ada "peluru perak". Tidak ada kisi yang mencakup semua persyaratan kami. Diputuskan untuk menulis kisi kami sendiri, yang dalam hal fungsionalitas kami akan mengembangkan ke arah yang kami butuhkan, dan berteman dengan teknologi yang dibutuhkan oleh produk kami.
Mengembangkan Kotak Data Bereaksi Anda
Pengembangan grid dimulai dengan prototipe, di mana mereka menguji topik yang paling sulit bagi kami:
- gulir virtual
- penyimpanan semua data kisi di Redux
Yang paling sulit ternyata adalah pengguliran virtual. Sebagian besar, itu dibuat dalam salah satu dari 3 cara:
1. Virtualisasi halaman
Data diambil dalam bagian - halaman. Saat menggulir, halaman yang terlihat ditambahkan, yang tidak terlihat dihapus. Halaman ini terdiri dari 20-60 baris (biasanya ukurannya dapat disesuaikan). Di sinilah produk pergi: DevExtreme JavaScript Data Grid, MS Fabric DetailsList.

2. Virtualisasi baris demi baris
Hanya garis yang terlihat yang ditarik. Begitu garis meninggalkan layar, itu segera dihapus. Produk berjalan seperti ini: Kotak ReactVirtualized, Kotak React DevExtreme, Kotak Telerik.

3. Kanvas
Semua garis dan isinya digambar menggunakan Kanvas. Inilah yang dilakukan Google Documents.

Saat mengembangkan kisi, kami membuat prototipe untuk ketiga opsi virtualisasi (bahkan untuk Canvas). Dan mereka memilih virtualisasi halaman per halaman.
Mengapa meninggalkan opsi lain?
Virtualisasi baris demi baris memiliki masalah dengan kecepatan rendering dalam prototipe. Begitu isi baris menjadi lebih rumit (banyak teks, sorot, pemangkasan, ikon, sejumlah besar kolom, dan di mana-mana flexbox), menjadi mahal untuk menambah / menghapus garis beberapa kali per detik. Tentu saja, hasilnya juga tergantung pada browser (kami membuat dukungan, termasuk untuk ie11, edge):

Opsi Canvas sangat menggoda dalam kecepatan rendering, tetapi melelahkan. Diusulkan untuk menggambar semuanya: teks, pembungkus teks, pemangkasan teks, highlighting, ikon, garis pemisah, highlighting, indentasi. Buat reaksi dengan mengklik tombol mouse pada Canvas, sorot garis ketika Anda mengarahkan kursor. Pada saat yang sama, beberapa elemen Dom (menunjukkan petunjuk, "tindakan pop-up" di telepon) harus diterapkan di atas Kanvas. Itu masih perlu untuk memecahkan masalah teks kabur dan ikon di Canvas. Semua ini panjang dan sulit dilakukan. Meskipun kami menguasai prototipe. Pada saat yang sama, setiap kustomisasi baris dan sel di masa depan akan menghasilkan kesusahan besar bagi kita.
Manfaat paging
Virtualisasi halaman-demi-halaman yang dipilih memiliki kelebihan dibandingkan dengan baris-demi-baris, yang menentukan pilihannya:
- Jika halaman sudah dirender, maka menggulir ke dalam halaman itu murah (pohon DOM tidak berubah saat menggulir). Virtualisasi baris demi baris untuk setiap pengguliran kecil membutuhkan perubahan pohon DOM, dan ini mahal ketika pohon DOM rumit dan flexbox digunakan di mana-mana.
- Untuk daftar kecil (<200 entri) halaman tidak dapat dihapus, cukup tambahkan. Cepat atau lambat, semua halaman akan dibangun, dan menggulir akan sepenuhnya gratis (dalam hal waktu render).
Pemilihan ukuran halaman
Masalah terpisah adalah pilihan ukuran halaman. Saya menulis di atas bahwa ukurannya dapat disesuaikan dan biasanya adalah 20-60 baris. Halaman besar digambar untuk waktu yang lama, halaman kecil mengarah ke tampilan sering "layar putih" saat menggulir. Secara eksperimental, ukuran halaman 25 baris telah dipilih. Namun, untuk ie11 ukurannya telah dikurangi menjadi 5 baris. Terasa seperti antarmuka di IE lebih responsif jika Anda menggambar banyak halaman kecil dengan penundaan kecil daripada yang besar dengan penundaan besar.
Virtualisasi halaman harus diimplementasikan menggunakan reaksi. Untuk melakukan ini, beberapa tugas harus diselesaikan:
Tugas 1. Bagaimana cara menambah / menghapus halaman melalui reaksi saat menggulir?
Untuk mengatasi masalah ini, konsep berikut diperkenalkan:
- model halaman
- tampilan halaman
Model adalah informasi untuk membangun pandangan. Tampilan adalah komponen Bereaksi.

Bahkan, tugas virtualisasi setelah ini adalah memanipulasi model halaman: menyimpan daftar model halaman, menambah dan menghapus model saat menggulir. Dan sudah dari daftar model melalui reaksi membangun / membangun kembali tampilan:

Dalam proses implementasi, aturan untuk bekerja dengan model halaman dibentuk:
- Halaman harus ditambahkan satu per satu. Setelah setiap penambahan, berikan waktu untuk menggambar. Dapat diterima untuk menambahkan 1 halaman setiap 300-500 ms - ini adalah situasi pengguliran yang cepat. Jika Anda menambahkan, misalnya, 5 halaman sekaligus, maka antarmuka pengguna hang pada konstruksinya.
- Halaman tidak perlu disimpan dalam lusinan. Contoh situasi masalah: 20 halaman ditampilkan, pengguna pergi ke daftar lain dan semua 20 halaman harus dihapus sekaligus. Menghapus sejumlah besar halaman adalah operasi yang mahal, membersihkan pohon DOM akan memakan waktu 1 detik. Untuk menghindari ini, lebih baik menyimpan tidak lebih dari 10 halaman sekaligus.
- Untuk setiap manipulasi kolom (penataan ulang, penambahan, penghapusan, peregangan) lebih baik untuk menghapus halaman yang tidak terlihat oleh pengguna sebelumnya. Ini akan menghindari pembangunan kembali yang mahal dari semua halaman yang dirender.
Tugas 2. Bagaimana cara menampilkan scollbar?
Pengguliran virtual mengasumsikan bahwa bilah gulir tersedia, yang memperhitungkan ukuran daftar dan memungkinkan Anda untuk menggulir ke mana saja:

Bagaimana cara menampilkan scollbar? Solusi paling sederhana adalah dengan menggambar div yang tidak terlihat dari ukuran yang dibutuhkan, bukan data nyata. Dan sudah di atas div ini kami menampilkan halaman yang terlihat:

Tugas 3. Bagaimana cara memantau ukuran viewport?
Viewport adalah area data yang terlihat dari grid. Mengapa mengawasi ukuran tubuhnya? Untuk menghitung jumlah halaman yang perlu ditampilkan kepada pengguna. Misalkan kita memiliki ukuran halaman kecil (5 baris) dan resolusi layar besar (1920x1080). Berapa banyak halaman yang perlu ditampilkan oleh pengguna untuk menutup seluruh viewport?

Anda dapat memecahkan masalah ini jika Anda tahu ketinggian viewport dan tinggi satu halaman. Sekarang mari kita rumit tugasnya, misalkan pengguna mengubah skala secara tajam di browser - set 50%:

Situasi dengan skala menunjukkan bahwa tidak cukup untuk mengetahui ukuran viewport sekali, Anda perlu memonitor ukurannya. Dan sekarang kami akan merumitkan tugas sepenuhnya: elemen html tidak memiliki acara pengubahan ukuran, yang dapat Anda ikuti dan pantau ukurannya. Hanya objek jendela yang telah diubah ukurannya.
Hal pertama yang terlintas dalam pikiran adalah menggunakan timer dan terus-menerus mengumpulkan tinggi elemen html. Tetapi ada solusi yang lebih baik yang kami lihat dengan DevExtreme JavaScript Data Grid: buat iframe yang tak terlihat, rentangkan ke ukuran kisi dan berlangganan acara pengubahan ukuran iframe.contentWindow:


Ringkasan
PS Ini bukan akhir. Pada artikel selanjutnya saya akan menceritakan bagaimana kami berteman dengan redux.
Untuk mendapatkan gulir virtual penuh, banyak tugas lain harus diselesaikan. Tetapi yang dijelaskan di atas adalah yang paling menarik. Berikut beberapa tugas lain yang juga muncul:
- Mempertimbangkan arah dan kecepatan bergulir ketika menambah / menghapus halaman.
- Memperhatikan perubahan data akun untuk meminimalkan pembuatan kembali model halaman. Misalnya, menghapus satu baris, atau menambahkan satu baris, apa yang harus dilakukan dengan halaman yang sudah dirender? Buang semuanya, atau tinggalkan beberapa? Ada ruang untuk optimasi.
- Saat mengubah pilihan, atur ulang jumlah halaman minimum yang diperlukan.
Jika Anda memiliki pertanyaan tentang implementasi, Anda dapat menuliskannya di komentar.