Erstellen von sortierbaren Tabellen mit React
Veröffentlicht: 2022-03-10Das Sortieren von Tabellen war schon immer ein ziemlich schwieriges Problem. Es gibt eine Menge Interaktionen zu verfolgen, umfangreiche DOM-Mutationen und sogar komplizierte Sortieralgorithmen. Es ist nur eine dieser Herausforderungen, die schwer zu bewältigen sind. Rechts?
Anstatt externe Bibliotheken hinzuzuziehen, versuchen wir, Dinge selbst zu erstellen. In diesem Artikel erstellen wir eine wiederverwendbare Methode zum Sortieren Ihrer tabellarischen Daten in React. Wir werden jeden Schritt im Detail durchgehen und dabei eine Reihe nützlicher Techniken lernen.
Wir werden die grundlegende React- oder JavaScript-Syntax nicht durchgehen, aber Sie müssen kein React-Experte sein, um mitzumachen.
Erstellen einer Tabelle mit React
Lassen Sie uns zunächst eine Beispieltabellenkomponente erstellen. Es akzeptiert eine Reihe von Produkten und gibt eine sehr einfache Tabelle aus, die eine Zeile pro Produkt auflistet.
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> ); }
Hier akzeptieren wir eine Reihe von Produkten und schleifen sie in unseren Tisch. Es ist statisch und im Moment nicht sortierbar, aber das ist jetzt in Ordnung.
Sortieren der Daten
Wenn Sie allen Whiteboard-Interviewern glauben würden, würden Sie denken, dass Softwareentwicklung fast ausschließlich aus Sortieralgorithmen besteht. Glücklicherweise werden wir uns hier nicht mit einer schnellen Sortierung oder Blasensortierung befassen.
Das Sortieren von Daten in JavaScript ist dank der integrierten Array-Funktion sort()
ziemlich einfach. Es sortiert Arrays von Zahlen und Strings ohne ein zusätzliches Argument:
const array = ['mozzarella', 'gouda', 'cheddar']; array.sort(); console.log(array); // ['cheddar', 'gouda', 'mozzarella']
Wenn Sie etwas Clevereres wollen, können Sie ihm eine Sortierfunktion übergeben. Diese Funktion erhält zwei Elemente in der Liste als Argumente und platziert eines vor dem anderen, je nachdem, was Sie entscheiden.
Beginnen wir damit, die erhaltenen Daten alphabetisch nach Namen zu sortieren.
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> ); }
Also, was ist hier los? Zuerst erstellen wir eine Kopie der Produktrequisite, die wir nach Belieben ändern und ändern können. Wir müssen dies tun, weil die Funktion Array.prototype.sort
das ursprüngliche Array ändert, anstatt eine neue sortierte Kopie zurückzugeben.
Als nächstes rufen wir sortedProducts.sort
auf und übergeben ihm eine sorting
. Wir prüfen, ob die name
des ersten Arguments a
vor dem zweiten Argument b
steht, und geben in diesem Fall einen negativen Wert zurück. Dies zeigt an, dass a
in der Liste vor b
stehen sollte. Wenn der Name des ersten Arguments nach dem Namen des zweiten Arguments steht, geben wir eine positive Zahl zurück, die angibt, dass wir b
vor a
setzen sollten. Wenn die beiden gleich sind (dh beide denselben Namen haben), geben wir 0
zurück, um die Reihenfolge beizubehalten.
Unseren Tisch sortierbar machen
Jetzt können wir also sicherstellen, dass die Tabelle nach Namen sortiert ist – aber wie können wir die Sortierreihenfolge selbst ändern?
Um zu ändern, nach welchem Feld wir sortieren, müssen wir uns das aktuell sortierte Feld merken. Das machen wir mit dem useState
Hook.
Ein Hook ist eine spezielle Art von Funktion, mit der wir uns in einige der Kernfunktionen von React „einhängen“ können, wie z. B. das Verwalten des Status und das Auslösen von Seiteneffekten. Dieser spezielle Haken ermöglicht es uns, einen Teil des internen Zustands in unserer Komponente beizubehalten und ihn zu ändern, wenn wir wollen. Folgendes werden wir hinzufügen:
const [sortedField, setSortedField] = React.useState(null);
Wir beginnen damit, überhaupt nichts zu sortieren. Als Nächstes ändern wir die Tabellenüberschriften, um eine Möglichkeit zu bieten, das Feld zu ändern, nach dem wir sortieren möchten.
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> ); };
Wenn wir jetzt auf eine Tabellenüberschrift klicken, aktualisieren wir das Feld, nach dem wir sortieren möchten. Ordentlich-o!
Wir führen jedoch noch keine eigentliche Sortierung durch, also lassen Sie uns das beheben. Erinnerst du dich an den Sortieralgorithmus von vorher? Hier ist es, nur leicht verändert, um mit einem unserer Feldnamen zu arbeiten.
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>
Wir stellen zunächst sicher, dass wir ein Feld zum Sortieren ausgewählt haben, und wenn ja, sortieren wir die Produkte nach diesem Feld.
Aufsteigend vs. absteigend
Das nächste Feature, das wir sehen möchten, ist eine Möglichkeit, zwischen aufsteigender und absteigender Reihenfolge zu wechseln. Wir wechseln zwischen aufsteigender und absteigender Reihenfolge, indem wir noch einmal auf die Tabellenüberschrift klicken.
Um dies zu implementieren, müssen wir einen zweiten Zustand einführen – die Sortierreihenfolge. Wir werden unsere aktuelle Zustandsvariable sortedField
, um sowohl den Feldnamen als auch seine Richtung beizubehalten. Anstatt eine Zeichenfolge zu enthalten, enthält diese Zustandsvariable ein Objekt mit einem Schlüssel (dem Feldnamen) und einer Richtung. Wir werden es in sortConfig
umbenennen, um es etwas klarer zu machen.
Hier ist die neue Sortierfunktion:
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; });
Nun, wenn die Richtung „aufsteigend“ ist, machen wir es wie zuvor. Wenn dies nicht der Fall ist, machen wir das Gegenteil und geben uns eine absteigende Reihenfolge.
Als nächstes erstellen wir eine neue Funktion – requestSort
– die den Feldnamen akzeptiert und den Status entsprechend aktualisiert.
const requestSort = key => { let direction = 'ascending'; if (sortConfig.key === key && sortConfig.direction === 'ascending') { direction = 'descending'; } setSortConfig({ key, direction }); }
Wir müssen auch unsere Klick-Handler ändern, um diese neue Funktion zu verwenden!
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> );
Jetzt sehen wir langsam so aus, als wären die Funktionen vollständig, aber es gibt noch eine große Sache zu tun. Wir müssen sicherstellen, dass wir unsere Daten nur sortieren, wenn dies erforderlich ist. Derzeit sortieren wir alle unsere Daten bei jedem Rendering, was später zu allen möglichen Leistungsproblemen führen wird. Verwenden wir stattdessen den eingebauten useMemo
-Hook, um alle langsamen Teile auswendig zu lernen!
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]);
Falls Sie es noch nicht gesehen haben, useMemo
ist eine Möglichkeit, teure Berechnungen zwischenzuspeichern oder zu speichern. Bei gleicher Eingabe müssen die Produkte also nicht zweimal sortiert werden, wenn wir unsere Komponente aus irgendeinem Grund erneut rendern. Beachten Sie, dass wir eine neue Sortierung auslösen möchten, wenn sich unsere Produkte ändern oder sich das Feld oder die Richtung, nach der wir sortieren, ändert.
Das Einschließen unseres Codes in diese Funktion hat enorme Auswirkungen auf die Leistung unserer Tabellensortierung!
Alles wiederverwendbar machen
Eines der besten Dinge an Hooks ist, wie einfach es ist, Logik wiederverwendbar zu machen. Sie werden wahrscheinlich alle Arten von Tabellen in Ihrer Anwendung sortieren, und das gleiche Zeug immer wieder neu implementieren zu müssen, klingt nach einer Zwickmühle.
React hat diese Funktion namens benutzerdefinierte Hooks. Sie klingen schick, aber alles, was sie sind, sind reguläre Funktionen, die andere Hooks in sich verwenden. Lassen Sie uns unseren Code so umgestalten, dass er in einem benutzerdefinierten Hook enthalten ist, damit wir ihn überall verwenden können!
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 }; }
Dies ist so ziemlich Kopieren und Einfügen aus unserem vorherigen Code, mit ein wenig Umbenennung. useSortableData
akzeptiert die Elemente und einen optionalen anfänglichen Sortierstatus. Es gibt ein Objekt mit den sortierten Elementen und eine Funktion zum Neusortieren der Elemente zurück.
Unser Tabellencode sieht nun so aus:
const ProductsTable = (props) => { const { products } = props; const { items, requestSort } = useSortableData(products); return ( <table>{/* ... */}</table> ); };
Eine letzte Berührung
Es fehlt ein kleines Stück – eine Möglichkeit, anzugeben, wie die Tabelle sortiert ist. Um dies in unserem Design anzuzeigen, müssen wir auch den internen Zustand zurückgeben – die sortConfig
. Lassen Sie uns das auch zurückgeben und es verwenden, um Stile zu generieren, die wir auf unsere Tabellenüberschriften anwenden können!
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> ); };
Und damit sind wir fertig!
Einpacken
Wie sich herausstellte, war das Erstellen eines eigenen Tabellensortieralgorithmus doch kein unmögliches Unterfangen. Wir haben einen Weg gefunden, unseren Zustand zu modellieren, wir haben eine generische Sortierfunktion geschrieben, und wir haben einen Weg geschrieben, unsere Sortiereinstellungen zu aktualisieren. Wir haben dafür gesorgt, dass alles leistungsfähig ist, und alles in einen benutzerdefinierten Hook umgestaltet. Schließlich haben wir eine Möglichkeit bereitgestellt, dem Benutzer die Sortierreihenfolge anzuzeigen.
Sie können eine Demo der Tabelle in dieser CodeSandbox sehen: