包会,使用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>
  );
};

感谢您的阅读。获取本文源码和在线体验,请点击这里


主要是大前端技术以及程序员成长精进相关内容,文章首发于同名公众号,若是你想第一时间接收最新文章,那么能够扫码关注。若是对你有一点点帮助,能够点喜欢点赞点收藏,还能够小额打赏做者,以鼓励做者写出更多更好的文章。

如今关注还送前端精品视频课程大礼包,准能为你节省很多钱。

相关文章
相关标签/搜索