包會,使用React Hook一步一步建立一個可排序表格組件

我花了一些精力來創做本文,以及熬夜編寫本文的示例程序,以便您能在閱讀以後能夠實踐參考,閱讀後若是以爲對您有幫助,能夠關注做者、收藏和點贊本文,這是對做者寫出優質文章最大的鼓勵了。javascript

在本文中,我將建立一種可重用的方法來對 React 中的表格數據進行排序功能,而且使用React Hook的方式編寫。我將詳細介紹每一個步驟,並在此過程當中學習一系列有用的技術,如 useStateuseMemo自定義Hook 的使用。前端

本文不會介紹基本的 React 或 JavaScript 語法,但你沒必要是 React 方面的專家也能跟上,最終咱們的效果以下。java

第一步,用 React 建立表格

首先,讓咱們建立一個表格組件,它將接受一個產品(product)數組,並輸出一個很是基本的表,每一個產品列出一行。react

function ProductTable(props) {
  const { products } = props;
  return (
    <table>
      <caption>Our products</caption>
      <thead>
        <tr>
          <th>名稱</th>
          <th>價格</th>
          <th>庫存數量</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>
  );
}

在這裏,咱們接受一個產品數組,並將它們循環到表中,它是靜態的。程序員

第二步,對數據進行排序

得益於內置的數組函數 sort(), JavaScript 中的數據排序很是簡單。它將對數字和字符串數組進行排序,而無需額外的參數:算法

const array = ["mozzarella", "gouda", "cheddar"];
array.sort();
console.log(array); // ['cheddar', 'gouda', 'mozzarella']

首先,按照名稱的字母順序對數據進行排序。segmentfault

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>;
}

這裏首先建立了一個 products 的副本,咱們能夠根據須要更改和更改。咱們須要這樣作,由於 Array.prototype.sort 函數會更改原始數組,而不是返回新的排序後的副本。數組

接下來,咱們調用 sortedProducts.sort,並將其傳遞給排序函數。咱們檢查第一個參數 aname 屬性是否在第二個參數b 以前,若是是,則返回負值,這表示列表中 a 應該在 b 以前。若是第一個參數的名稱在第二個參數的名稱以後,咱們將返回一個正數,表示應將 b 放在 a 以前。若是二者相等(即名稱相同),咱們將返回 0 以保留順序。緩存

第三步,使咱們的表格可排序

因此如今咱們能夠確保表是按名稱排序的——可是咱們如何改變排序順序呢?要更改排序依據的字段,咱們須要記住當前排序的字段。咱們將使用 useState Hook函數

一開始咱們什麼都不排序。接下來,讓咱們更改表標題,以包含一種方法來更改咱們想要排序的字段。

import React, { useState } from "react";
const ProductsTable = props => {
  const { products } = props;
  const [sortedField, setSortedField] = useState(null);
  return (
    <table>
      <thead>
        <tr>
          <th>
            <button type="button" onClick={() => setSortedField("name")}>
              名稱
            </button>
          </th>
          <th>
            <button type="button" onClick={() => setSortedField("price")}>
              價格
            </button>
          </th>
          <th>
            <button type="button" onClick={() => setSortedField("stock")}>
              庫存數量
            </button>
          </th>
        </tr>
      </thead>
      {/* 像以前同樣 */}
    </table>
  );
};

如今,每當咱們單擊一個表標題時,咱們都會更新要排序的字段。

咱們尚未作任何實際的排序,咱們繼續。還記得以前的排序算法嗎?這裏只是稍微修改了一下,以便與咱們的字段名一塊兒使用。

import React, { useState } from "react";
const ProductsTable = (props) => {
  const { products } = props;
  const [sortedField, setSortedField] = 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>

首先,咱們要肯定咱們選擇了一個字段來排序,以後咱們將根據該字段對產品排序。

第四步,升序和降序操做

咱們要看到的下一個功能,是一種在升序和降序之間切換的方法,經過再次單擊表的標題項在升序和降序之間切換。

爲此,咱們須要引入第二種狀態:排序順序。咱們將重構當前的 sortedField 狀態變量,以保留字段名及其排序方向。該狀態變量將不包含字符串,而是包含一個帶有鍵(字段名稱)和排序方向的對象。咱們將其重命名爲 sortConfig,以使其更加清晰。

這是新的排序函數:

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;
});

如今,若是方向是「ascending(升序)」,咱們將像之前同樣進行。若是不是,咱們將採起相反的操做,以降序排列。

接下來,咱們將建立一個新函數 requestSort,它將接受字段名稱並相應地更新狀態。

const requestSort = key => {
  let direction = "ascending";
  if (sortConfig.key === key && sortConfig.direction === "ascending") {
    direction = "descending";
  }
  setSortConfig({ key, direction });
};

咱們還必須更改咱們的點擊事件處理函數才能使用此新功能!

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>
    {/* 像以前同樣 */}
  </table>
);

如今咱們看起來功能已經很完整了,可是還有一件重要的事情要作。咱們須要確保只在須要時纔對數據進行排序。目前,咱們正在對每一個渲染中的全部數據進行排序,這將致使各類各樣的性能問題。相反,讓咱們使用內置的 useMemo Hook 來記憶會致使緩慢的部分!

import React, { useState, useMemo } from "react";
const ProductsTable = (props) => {
  const { products } = props;
  const [sortConfig, setSortConfig] = useState(null);

  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]);

useMemo 是一種緩存或記憶昂貴計算的方法。給定相同的輸入,若是咱們出於某種緣由從新渲染組件,它沒必要對產品進行兩次排序。請注意,每當咱們的產品發生變化,或者根據變化對字段或排序方向進行排序時,咱們都但願觸發一個新的排序。

在這個函數中包裝咱們的代碼將對咱們的表排序產生巨大的性能影響!

優化,讓代碼可複用

對於 hooks 最好的做用就是使代碼複用變得很容易,React 具備稱爲自定義 Hook 的功能。它們聽起來很花哨,但它們都是常規函數,在其中使用了其餘 Hook。讓咱們將代碼重構爲包含在自定義 Hook 中,這樣咱們就能夠處處使用它了!

import React, { useState, useMemo } from "react";

const useSortableData = (items, config = null) => {
  const [sortConfig, setSortConfig] = useState(config);

  const sortedItems = 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.key === key && sortConfig.direction === "ascending") {
      direction = "descending";
    }
    setSortConfig({ key, direction });
  };

  return { items, requestSort };
};

這幾乎是咱們先前代碼的複製和粘貼,並引入了一些重命名。useSortableData 接受 items 和一個可選的初始排序狀態。它返回一個帶有已排序 items 的對象和一個用於從新排序 items 的函數。

咱們的表代碼如今看起來像這樣:

const ProductsTable = props => {
  const { products } = props;
  const { items, requestSort } = useSortableData(products);
  return <table>{/* ... */}</table>;
};

最後一點

缺乏一小部分,一種指示表格如何排序的方法。爲了代表這一點,在咱們的設計中,咱們還須要返回內部狀態 sortConfig。讓咱們返回它,並使用它來生成樣式以應用到咱們的表格標題!

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>
  );
};

感謝您的閱讀。獲取本文源碼和在線體驗,請點擊這裏


主要是大前端技術以及程序員成長精進相關內容,文章首發於同名公衆號,若是你想第一時間接收最新文章,那麼能夠掃碼關注。若是對你有一點點幫助,能夠點喜歡點贊點收藏,還能夠小額打賞做者,以鼓勵做者寫出更多更好的文章。

如今關注還送前端精品視頻課程大禮包,準能爲你節省很多錢。

相關文章
相關標籤/搜索