Crearea de tabele sortabile cu React
Publicat: 2022-03-10Sortarea meselor a fost întotdeauna o problemă destul de greu de rezolvat. Există o mulțime de interacțiuni de urmărit, mutații DOM extinse de făcut și chiar algoritmi de sortare complicati. Este doar una dintre acele provocări greu de rezolvat. Dreapta?
În loc să atragem biblioteci externe, să încercăm să facem noi înșine. În acest articol, vom crea o modalitate reutilizabilă de a vă sorta datele tabulare în React. Vom parcurge fiecare pas în detaliu și vom învăța o grămadă de tehnici utile pe parcurs.
Nu vom trece prin sintaxa de bază React sau JavaScript, dar nu trebuie să fii un expert în React pentru a urma.
Crearea unui tabel cu React
Mai întâi, să creăm un exemplu de componentă a tabelului. Va accepta o gamă de produse și va scoate un tabel foarte simplu, listând un rând per produs.
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> ); }
Aici, acceptăm o serie de produse și le introducem în tabelul nostru. Este static și nu poate fi sortat în acest moment, dar asta e în regulă pentru moment.
Sortarea Datelor
Dacă i-ai crede pe toți intervievatorii de la tablă, ai crede că dezvoltarea de software este aproape toți algoritmi de sortare. Din fericire, nu vom căuta aici o sortare rapidă sau o sortare cu bule.
Sortarea datelor în JavaScript este destul de simplă, datorită funcției matrice încorporate sort()
. Va sorta matrice de numere și șiruri de caractere fără un argument suplimentar:
const array = ['mozzarella', 'gouda', 'cheddar']; array.sort(); console.log(array); // ['cheddar', 'gouda', 'mozzarella']
Dacă vrei ceva mai inteligent, îi poți trece o funcție de sortare. Această funcție are două elemente din listă ca argumente și va plasa unul în fața celuilalt în funcție de ceea ce decideți.
Să începem prin a sorta datele pe care le primim alfabetic după nume.
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> ); }
Deci ce se întâmplă aici? În primul rând, creăm o copie a suportului produselor, pe care o putem modifica și modifica după bunul plac. Trebuie să facem acest lucru deoarece funcția Array.prototype.sort
modifică matricea originală în loc să returneze o nouă copie sortată.
Apoi, numim sortedProducts.sort
și îi transmitem o funcție de sorting
. Verificăm dacă proprietatea name
a primului argument a
este înaintea celui de-al doilea argument b
și, dacă da, returnăm o valoare negativă. Aceasta indică faptul că a
ar trebui să fie înaintea lui b
în listă. Dacă numele primului argument este după numele celui de-al doilea argument, returnăm un număr pozitiv, indicând că ar trebui să plasăm b
înaintea a
. Dacă cele două sunt egale (adică ambele au același nume), returnăm 0
pentru a păstra ordinea.
Facem ca masa noastră să fie sortată
Deci, acum ne putem asigura că tabelul este sortat după nume - dar cum putem schimba singuri ordinea de sortare?
Pentru a schimba câmpul după ce sortăm, trebuie să ne amintim câmpul sortat în prezent. Vom face asta cu cârligul useState
.
Un cârlig este un tip special de funcție care ne permite să ne „prindem” la unele dintre funcționalitățile de bază ale React, cum ar fi gestionarea stării și declanșarea efectelor secundare. Acest cârlig special ne permite să menținem o parte din starea internă a componentei noastre și să o schimbăm dacă dorim. Iată ce vom adăuga:
const [sortedField, setSortedField] = React.useState(null);
Începem prin a nu sorta absolut nimic. În continuare, să modificăm titlurile tabelului pentru a include o modalitate de a schimba câmpul după care dorim să sortăm.
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> ); };
Acum, ori de câte ori facem clic pe un titlu de tabel, actualizăm câmpul după care dorim să sortăm. Neat-o!
Încă nu facem nicio sortare reală, așa că haideți să rezolvăm asta. Vă amintiți algoritmul de sortare de mai înainte? Iată-l, doar ușor modificat pentru a funcționa cu oricare dintre numele câmpurilor noastre.
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>
Mai întâi ne asigurăm că am ales un câmp după care să sortăm și, dacă da, sortăm produsele după câmpul respectiv.
Crescator vs Descendent
Următoarea caracteristică pe care vrem să o vedem este o modalitate de a comuta între ordinea crescătoare și descendentă. Vom comuta între ordinea crescătoare și descrescătoare făcând clic încă o dată pe titlul tabelului.
Pentru a implementa acest lucru, va trebui să introducem o a doua parte de stare - ordinea de sortare. Vom refactoriza variabila noastră de stare sortedField
curentă pentru a păstra atât numele câmpului, cât și direcția acestuia. În loc să conțină un șir, această variabilă de stare va conține un obiect cu o cheie (numele câmpului) și o direcție. Îl vom redenumi în sortConfig
pentru a fi puțin mai clar.
Iată noua funcție de sortare:
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; });
Acum, dacă direcția este „ascendente”, vom face așa cum am făcut anterior. Dacă nu este, vom face invers, oferindu-ne o ordine descrescătoare.
În continuare, vom crea o nouă funcție — requestSort
— care va accepta numele câmpului și va actualiza starea în consecință.
const requestSort = key => { let direction = 'ascending'; if (sortConfig.key === key && sortConfig.direction === 'ascending') { direction = 'descending'; } setSortConfig({ key, direction }); }
De asemenea, va trebui să ne schimbăm gestionanții de clic pentru a folosi această nouă funcție!
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> );
Acum începem să arătăm destul de complet complet, dar mai rămâne un lucru important de făcut. Trebuie să ne asigurăm că ne sortăm datele doar atunci când avem nevoie. În prezent, sortăm toate datele noastre la fiecare randare, ceea ce va duce la tot felul de probleme de performanță pe linie. În schimb, să folosim cârligul useMemo
încorporat pentru a memoriza toate părțile lente!
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]);
Dacă nu l-ați văzut până acum, useMemo
este o modalitate de a stoca în cache - sau de a memoriza - calcule costisitoare. Deci, având în vedere aceeași intrare, nu trebuie să sorteze produsele de două ori dacă ne redăm din nou componenta dintr-un motiv oarecare. Rețineți că dorim să declanșăm o nouă sortare ori de câte ori produsele noastre se schimbă sau câmpul sau direcția în care sortăm după modificări.
Încheierea codului nostru în această funcție va avea implicații uriașe de performanță pentru sortarea tabelelor!
Făcând totul reutilizabil
Unul dintre cele mai bune lucruri despre cârlige este cât de ușor este să faci logica reutilizabilă. Probabil că veți sorta toate tipurile de tabele de-a lungul aplicației dvs. și a trebui să reimplementați aceleași chestii din nou sună ca o tragedie.
React are această caracteristică numită cârlige personalizate. Sună elegant, dar tot ce sunt sunt funcții obișnuite care folosesc alte cârlige în interiorul lor. Să refactorăm codul nostru pentru a fi conținut într-un cârlig personalizat, astfel încât să-l putem folosi peste tot!
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 }; }
Acesta este aproape copiați și lipiți din codul nostru anterior, cu puțină redenumire introdusă. useSortableData
acceptă elementele și o stare de sortare inițială opțională. Returnează un obiect cu elementele sortate și o funcție de resortare.
Codul nostru de tabel acum arată astfel:
const ProductsTable = (props) => { const { products } = props; const { items, requestSort } = useSortableData(products); return ( <table>{/* ... */}</table> ); };
O ultimă atingere
Lipsește o bucată minusculă - o modalitate de a indica modul în care este sortat masa. Pentru a indica faptul că în designul nostru, trebuie să returnăm și starea internă - sortConfig
. Să returnăm și asta și să-l folosim pentru a genera stiluri pe care le putem aplica titlurilor tabelelor noastre!
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> ); };
Și cu asta, am terminat!
Încheierea
După cum se dovedește, crearea propriului algoritm de sortare a tabelelor nu a fost o faptă imposibilă până la urmă. Am găsit o modalitate de a ne modela starea, am scris o funcție de sortare generică și am scris o modalitate de a actualiza care sunt preferințele noastre de sortare. Ne-am asigurat că totul este performant și am refactorizat totul într-un cârlig personalizat. În cele din urmă, am oferit o modalitate de a indica utilizatorului ordinea de sortare.
Puteți vedea o demonstrație a tabelului în acest CodeSandbox: