Membuat Tabel yang Dapat Diurutkan Dengan React
Diterbitkan: 2022-03-10Penyortiran tabel selalu menjadi masalah yang cukup sulit untuk diperbaiki. Ada banyak interaksi yang harus dipantau, mutasi DOM ekstensif yang harus dilakukan, dan bahkan algoritme penyortiran yang rumit juga. Itu hanya salah satu tantangan yang sulit untuk diselesaikan. Benar?
Alih-alih menarik perpustakaan eksternal, mari kita coba membuatnya sendiri. Dalam artikel ini, kami akan membuat cara yang dapat digunakan kembali untuk mengurutkan data tabular Anda di React. Kita akan melalui setiap langkah secara mendetail, dan mempelajari banyak teknik yang berguna di sepanjang jalan.
Kami tidak akan membahas sintaks React atau JavaScript dasar, tetapi Anda tidak harus menjadi ahli dalam React untuk mengikutinya.
Membuat Tabel Dengan React
Pertama, mari kita buat komponen tabel sampel. Ini akan menerima serangkaian produk, dan menampilkan tabel yang sangat mendasar, mencantumkan baris per produk.
function ProductTable(props) { const { products } = props; return ( <table> <caption>Our products</caption> <thead> <tr> <th>Name</th> <th>Price</th> <th>In Stock</th> </tr> </thead> <tbody> {products.map(product => ( <tr key={product.id}> <td>{product.name}</td> <td>{product.price}</td> <td>{product.stock}</td> </tr> ))} </tbody> </table> ); }
Di sini, kami menerima serangkaian produk dan memasukkannya ke dalam tabel kami. Ini statis dan tidak dapat diurutkan saat ini, tetapi tidak apa-apa untuk saat ini.
Menyortir Data
Jika Anda percaya semua pewawancara papan tulis, Anda akan berpikir pengembangan perangkat lunak hampir semua algoritma penyortiran. Untungnya, kami tidak akan mencari pengurutan cepat atau pengurutan gelembung di sini.
Menyortir data dalam JavaScript cukup mudah, berkat fungsi array bawaan sort()
. Itu akan mengurutkan array angka dan string tanpa argumen tambahan:
const array = ['mozzarella', 'gouda', 'cheddar']; array.sort(); console.log(array); // ['cheddar', 'gouda', 'mozzarella']
Jika Anda menginginkan sesuatu yang sedikit lebih pintar, Anda dapat memberikannya fungsi penyortiran. Fungsi ini diberikan dua item dalam daftar sebagai argumen, dan akan menempatkan satu di depan yang lain berdasarkan apa yang Anda putuskan.
Mari kita mulai dengan menyortir data yang kita peroleh berdasarkan abjad berdasarkan nama.
function ProductTable(props) { const { products } = props; let sortedProducts = [...products]; sortedProducts.sort((a, b) => { if (a.name < b.name) { return -1; } if (a.name > b.name) { return 1; } return 0; }); return ( <Table> {/* as before */} </Table> ); }
Jadi apa yang terjadi di sini? Pertama, kita membuat salinan prop produk, yang dapat kita ubah dan ubah sesuka kita. Kita perlu melakukan ini karena fungsi Array.prototype.sort
mengubah array asli alih-alih mengembalikan salinan baru yang diurutkan.
Selanjutnya, kita panggil sortedProducts.sort
, dan berikan fungsi sorting
. Kami memeriksa apakah properti name
dari argumen pertama a
sebelum argumen kedua b
, dan jika demikian, mengembalikan nilai negatif. Ini menunjukkan bahwa a
harus didahulukan sebelum b
dalam daftar. Jika nama argumen pertama setelah nama argumen kedua, kami mengembalikan angka positif, yang menunjukkan bahwa kami harus menempatkan b
sebelum a
. Jika keduanya sama (yaitu keduanya memiliki nama yang sama), kami mengembalikan 0
untuk mempertahankan urutannya.
Membuat Meja Kami Dapat Diurutkan
Jadi sekarang kita dapat memastikan bahwa tabel diurutkan berdasarkan nama — tetapi bagaimana kita dapat mengubah sendiri urutan pengurutannya?
Untuk mengubah bidang apa yang kita urutkan, kita perlu mengingat bidang yang saat ini diurutkan. Kami akan melakukannya dengan kait useState
.
Hook adalah jenis fungsi khusus yang memungkinkan kita "menghubungkan" ke beberapa fungsi inti React, seperti mengelola status dan memicu efek samping. Kait khusus ini memungkinkan kita mempertahankan bagian dari status internal di komponen kita, dan mengubahnya jika kita mau. Inilah yang akan kami tambahkan:
const [sortedField, setSortedField] = React.useState(null);
Kami mulai dengan tidak menyortir apa pun. Selanjutnya, mari kita ubah judul tabel untuk menyertakan cara mengubah bidang apa yang ingin kita urutkan.
const ProductsTable = (props) => { const { products } = props; const [sortedField, setSortedField] = React.useState(null); return ( <table> <thead> <tr> <th> <button type="button" onClick={() => setSortedField('name')}> Name </button> </th> <th> <button type="button" onClick={() => setSortedField('price')}> Price </button> </th> <th> <button type="button" onClick={() => setSortedField('stock')}> In Stock </button> </th> </tr> </thead> {/* As before */} </table> ); };
Sekarang, setiap kali kita mengklik judul tabel, kita memperbarui bidang yang ingin kita urutkan. rapi-o!
Kami belum melakukan penyortiran yang sebenarnya, jadi mari kita perbaiki itu. Ingat algoritma pengurutan dari sebelumnya? Ini dia, hanya sedikit diubah untuk bekerja dengan salah satu nama bidang kami.
const ProductsTable = (props) => { const { products } = props; const [sortedField, setSortedField] = React.useState(null); let sortedProducts = [...products]; if (sortedField !== null) { sortedProducts.sort((a, b) => { if (a[sortedField] < b[sortedField]) { return -1; } if (a[sortedField] > b[sortedField]) { return 1; } return 0; }); } return ( <table>
Kami pertama-tama memastikan bahwa kami telah memilih bidang untuk diurutkan, dan jika demikian, kami mengurutkan produk berdasarkan bidang itu.
Naik vs Turun
Fitur berikutnya yang ingin kita lihat adalah cara untuk beralih antara urutan menaik dan menurun. Kita akan beralih antara urutan menaik dan menurun dengan mengklik judul tabel sekali lagi.
Untuk mengimplementasikan ini, kita perlu memperkenalkan bagian kedua dari status — urutan pengurutan. Kami akan memfaktorkan ulang variabel status sortedField
kami saat ini untuk mempertahankan nama bidang dan arahnya. Alih-alih berisi string, variabel status ini akan berisi objek dengan kunci (nama bidang) dan arah. Kami akan mengganti namanya menjadi sortConfig
agar sedikit lebih jelas.
Inilah fungsi penyortiran baru:
sortedProducts.sort((a, b) => { if (a[sortConfig.key] < b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? -1 : 1; } if (a[sortConfig.key] > b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? 1 : -1; } return 0; });
Sekarang, jika arahnya 'naik', kita akan melakukan seperti yang kita lakukan sebelumnya. Jika tidak, kami akan melakukan yang sebaliknya, memberi kami urutan menurun.
Selanjutnya, kita akan membuat fungsi baru — requestSort
— yang akan menerima nama bidang, dan memperbarui statusnya.
const requestSort = key => { let direction = 'ascending'; if (sortConfig.key === key && sortConfig.direction === 'ascending') { direction = 'descending'; } setSortConfig({ key, direction }); }
Kami juga harus mengubah penangan klik kami untuk menggunakan fungsi baru ini!
return ( <table> <thead> <tr> <th> <button type="button" onClick={() => requestSort('name')}> Name </button> </th> <th> <button type="button" onClick={() => requestSort('price')}> Price </button> </th> <th> <button type="button" onClick={() => requestSort('stock')}> In Stock </button> </th> </tr> </thead> {/* as before */} </table> );
Sekarang kami mulai terlihat cukup lengkap fiturnya, tetapi masih ada satu hal besar yang harus dilakukan. Kita perlu memastikan bahwa kita hanya menyortir data kita saat kita membutuhkannya. Saat ini, kami sedang menyortir semua data kami di setiap render, yang akan menyebabkan segala macam masalah kinerja di kemudian hari. Sebagai gantinya, mari gunakan kait useMemo
untuk memoize semua bagian yang lambat!
const ProductsTable = (props) => { const { products } = props; const [sortConfig, setSortConfig] = React.useState(null); React.useMemo(() => { let sortedProducts = [...products]; if (sortedField !== null) { sortedProducts.sort((a, b) => { if (a[sortConfig.key] < b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? -1 : 1; } if (a[sortConfig.key] > b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? 1 : -1; } return 0; }); } return sortedProducts; }, [products, sortConfig]);
Jika Anda belum pernah melihatnya, useMemo
adalah cara untuk men-cache — atau memoize — perhitungan yang mahal. Jadi dengan input yang sama, tidak perlu mengurutkan produk dua kali jika kita merender ulang komponen kita karena alasan tertentu. Perhatikan bahwa kami ingin memicu pengurutan baru setiap kali produk kami berubah, atau bidang atau arah yang kami urutkan berdasarkan perubahan.
Membungkus kode kita dalam fungsi ini akan memiliki implikasi kinerja yang besar untuk penyortiran tabel kita!
Membuat Semuanya Dapat Digunakan Kembali
Salah satu hal terbaik tentang hook adalah betapa mudahnya membuat logika dapat digunakan kembali. Anda mungkin akan menyortir semua jenis tabel di seluruh aplikasi Anda, dan harus mengimplementasikan ulang hal yang sama lagi terdengar seperti hambatan.
React memiliki fitur yang disebut custom hooks. Kedengarannya mewah, tetapi semua itu adalah fungsi biasa yang menggunakan kait lain di dalamnya. Mari kita refactor kode kita untuk dimasukkan ke dalam custom hook, jadi kita bisa menggunakannya di semua tempat!
const useSortableData = (items, config = null) => { const [sortConfig, setSortConfig] = React.useState(config); const sortedItems = React.useMemo(() => { let sortableItems = [...items]; if (sortConfig !== null) { sortableItems.sort((a, b) => { if (a[sortConfig.key] < b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? -1 : 1; } if (a[sortConfig.key] > b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? 1 : -1; } return 0; }); } return sortableItems; }, [items, sortConfig]); const requestSort = key => { let direction = 'ascending'; if (sortConfig && sortConfig.key === key && sortConfig.direction === 'ascending') { direction = 'descending'; } setSortConfig({ key, direction }); } return { items: sortedItems, requestSort }; }
Ini cukup banyak salin dan tempel dari kode kita sebelumnya, dengan sedikit perubahan nama. useSortableData
menerima item, dan status pengurutan awal opsional. Ini mengembalikan objek dengan item yang diurutkan, dan fungsi untuk mengurutkan ulang item.
Kode tabel kita sekarang terlihat seperti ini:
const ProductsTable = (props) => { const { products } = props; const { items, requestSort } = useSortableData(products); return ( <table>{/* ... */}</table> ); };
Sentuhan Terakhir
Ada satu bagian kecil yang hilang — cara untuk menunjukkan bagaimana tabel diurutkan. Untuk menunjukkan bahwa dalam desain kita, kita juga perlu mengembalikan keadaan internal — sortConfig
. Mari kita kembalikan itu juga, dan gunakan itu untuk menghasilkan gaya yang bisa kita terapkan ke judul tabel kita!
const ProductTable = (props) => { const { items, requestSort, sortConfig } = useSortableData(props.products); const getClassNamesFor = (name) => { if (!sortConfig) { return; } return sortConfig.key === name ? sortConfig.direction : undefined; }; return ( <table> <caption>Products</caption> <thead> <tr> <th> <button type="button" onClick={() => requestSort('name')} className={getClassNamesFor('name')} > Name </button> </th> {/* … */} </tr> </thead> {/* … */} </table> ); };
Dan dengan itu, kita selesai!
Membungkus
Ternyata, membuat algoritme penyortiran tabel Anda sendiri bukanlah hal yang mustahil. Kami menemukan cara untuk memodelkan status kami, kami menulis fungsi penyortiran umum, dan kami menulis cara untuk memperbarui preferensi penyortiran kami. Kami memastikan semuanya berkinerja baik dan memfaktorkan ulang semuanya menjadi kait khusus. Akhirnya, kami menyediakan cara untuk menunjukkan urutan pengurutan kepada pengguna.
Anda dapat melihat demo tabel di CodeSandbox ini: