专栏首页前端全栈开发者使用React Hook一步步教你创建一个可排序表格组件

使用React Hook一步步教你创建一个可排序表格组件

我花了一些精力来创作本文,以及熬夜编写本文的示例程序,以便您能在阅读之后可以实践参考,阅读后如果觉得对您有帮助,可以关注作者、收藏和点赞本文,这是对作者写出优质文章最大的鼓励了。

在本文中,我将创建一种可重用的方法来对 React 中的表格数据进行排序功能,并且使用React Hook的方式编写。我将详细介绍每个步骤,并在此过程中学习一系列有用的技术,如 useStateuseMemo自定义Hook 的使用。

本文不会介绍基本的 React 或 JavaScript 语法,但你不必是 React 方面的专家也能跟上,最终我们的效果如下。

第一步,用 React 创建表格

首先,让我们创建一个表格组件,它将接受一个产品(product)数组,并输出一个非常基本的表,每个产品列出一行。

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']

首先,按照名称的字母顺序对数据进行排序。

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

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


如果对你有所启发和帮助,可以点个关注、收藏,也可以留言讨论,这是对作者的最大鼓励。

作者简介:Web前端工程师,全栈开发工程师、持续学习者。

现在关注《前端外文精选》微信公众号,还送某网精品视频课程网盘资料啊,准能为你节省不少钱!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【译】7个简单但棘手的JavaScript面试问题

    如果您符合高级开发人员的资格,其工作涉及JavaScript,那么在编码面试中很有可能会被问到棘手的问题。

    张张
  • HTML页面生成器:使用JavaScript和Node创建CLI

    在上一篇文章:【实战】从零开始使用JavaScript制作自己的命令行(CLI工具) 中我介绍了如何从零开始制作CLI,那么现在我们更进一步。在这篇文章中,我们...

    张张
  • React.js和Vue.js的语法并列比较

    React.js和Vue.js都是很好的框架。而且Next.js和Nuxt.js甚至将它们带入了一个新的高度,这有助于我们以更少的配置和更好的可维护性来创建应用...

    张张
  • [剑指offer] 树的子结构

    输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

    尾尾部落
  • How do you compare two version Strings in Java?

    https://stackoverflow.com/questions/198431/how-do-you-compare-two-version-string...

    iOSDevLog
  • 腾讯iOA零信任安全——IT变革下的新一代企业网

    ? 导语:2019年5月21日,腾讯全球数字生态大会在春城昆明盛大开幕。大会中TEG作为腾讯的内部技术支撑平台,在展区展出30余个技术应用。共分为资源层,资源...

    腾讯技术工程官方号
  • 实力认证!腾讯主导确立全球首个零信任安全国际标准

    几天前,在瑞士日内瓦举办的ITU-T(国际电信联盟通信标准化组织)SG17安全研究组全体会议上,由腾讯主导的“服务访问过程持续保护参考框架”国际标准成功立项,成...

    腾讯安全
  • Linux 挂载分区的方法

    格式化完成以后需要对分区进行挂载,相当于Windows系统下分配盘符,没有挂载的分区是不能使用的。

    砸漏
  • ODL应用案例之云和网络功能虚拟化应用案例

    概述 近年来,大公司和中小企业已经认识到云计算技术是提高自己的竞争力的核心技术。混合云和公共云虽然具备了提高业务灵活性并降低总成本的潜力,但它们对于传统网络的需...

    SDNLAB
  • ODL应用案例之云和网络功能虚拟化应用案例

    概述 近年来,大公司和中小企业已经认识到云计算技术是提高自己的竞争力的核心技术。混合云和公共云虽然具备了提高业务灵活性并降低总成本的潜力,但它们对于传统网络的需...

    SDNLAB

扫码关注云+社区

领取腾讯云代金券