React+TS前台项目实战(二十五)-- 全局常用排序组件SortButton封装


前言

今天要封装的SortButton 组件,主要用在表格列排序上,运用于更新路由并跳转更新,起到刷新页面仍然处于当前排序数据。

SortButton组件

1. 功能分析

(1)用于显示一个可以点击的按钮,用于排序数据
(2)接受一个field参数,用于指定要排序的字段
(3)可选择性接收一个sortParam参数,用于传递排序参数,如果未提供,则使用useSortParam的默认结果
(4)点击按钮时会触发排序动作,由handleSortClick函数定义

2. 代码+详细注释

// @/components/SortButton/index.tsx
import { ReactComponent as SortIcon } from "../../assets/sort_icon.svg";
import { SortButtonContainer } from "./styled";
import { useSortParam } from "@/hooks";
/**
 * 排序按钮组件
 * @param field - 要排序的字段名
 * @param sortParam - 排序参数,可选,默认使用useSortParam的结果
 */
export function SortButton<T extends string>({ field, sortParam }: { field: T; sortParam?: ReturnType<typeof useSortParam<T>> }) {
  // 获取排序参数
  const sortParamByQuery = useSortParam();
  // 获取排序字段、排序方式和排序事件处理函数
  const { sortBy, orderBy, handleSortClick } = sortParam ?? sortParamByQuery;
  // 判断当前字段是否为激活状态
  const isActive = sortBy === field;

  // 点击按钮时的事件处理函数
  const handleClick = () => {
    handleSortClick(field);
  };

  return (
    // 渲染排序按钮
    <SortButtonContainer>
      <button type="button" data-order={isActive ? orderBy : null} onClick={handleClick}>
        <SortIcon />
      </button>
    </SortButtonContainer>
  );
}
export default SortButton;
------------------------------------------------------------------------------
// @/components/SortButton/styled.tsx
import styled from "styled-components";
export const SortButtonContainer = styled.div`
  appearance: none;
  border: none;
  outline: none;
  background: none;
  display: inline-flex;
  vertical-align: text-top;
  margin-left: 8px;
  cursor: pointer;

  &[data-order="desc"] {
    svg > path:first-child {
      fill: var(--cd-primary-color);
    }
  }

  &[data-order="asc"] {
    svg > path:last-child {
      fill: var(--cd-primary-color);
    }
  }
`;

3. 使用到的全局hook代码

import { useEffect, useState, useCallback, useMemo } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import { omit, omitNil } from "@/utils/object";

/**
 * @description 获取URL中的查询参数
 * @param search 查询参数字符串
 * @param names 需要获取的参数名称
 * @returns 参数对象
 */
function getSearchParams<T extends string = string>(search: string, names?: T[]): Partial<Record<T, string>> {
  const urlSearchParams = new URLSearchParams(search);
  const entries = [...urlSearchParams.entries()].filter((entry): entry is [T, string] => names == null || (names as string[]).includes(entry[0]));
  return Object.fromEntries(entries) as Partial<Record<T, string>>;
}

/**
 * @description 获取当前URL中的查询参数
 * @param names 需要获取的参数名称
 * @returns 参数对象
 */
export function useSearchParams<T extends string>(...names: T[]): Partial<Record<T, string>> {
  const location = useLocation();
  return useMemo(() => getSearchParams(location.search, names), [location.search, names]);
}

/**
 * @description 更新URL中的查询参数
 * @returns 更新后的URL
 */
export function useUpdateSearchParams<T extends string>(): (updater: (current: Partial<Record<T, string>>) => Partial<Record<T, string | null | undefined>>, replace?: boolean, routing?: boolean) => string {
  const navigate = useNavigate();
  const { search, pathname, hash } = useLocation();

  return useCallback(
    (updater, replace, routing = true) => {
      const oldParams: Partial<Record<T, string>> = getSearchParams(search);
      const newParams = omitNil(updater(oldParams));
      const newUrlSearchParams = new URLSearchParams(newParams as Record<string, string>);
      newUrlSearchParams.sort();
      const newQueryString = newUrlSearchParams.toString();
      const to = `${pathname}${newQueryString ? `?${newQueryString}` : ""}${hash}`;

      if (routing) {
        if (replace) {
          navigate.replace(to);
        } else {
          navigate.push(to);
        }
      }

      return to;
    },
    [hash, navigate, pathname, search]
  );
}

/**
 * @description 排序参数
 * @type SortType 排序字段类型
 */
export type OrderByType = "asc" | "desc";

/**
 * @description 获取排序参数
 * @param isSortBy 判断是否为排序字段的函数
 * @param defaultValue 默认排序字段
 * @returns 排序参数对象
 */
export function useSortParam<T extends string>(
  isSortBy?: (s?: string) => boolean,
  defaultValue?: string
): {
  sortBy: T | undefined;
  orderBy: OrderByType;
  sort?: string;
  handleSortClick: (sortRule?: T) => void;
} {
  type SortType = T | undefined;
  function isSortByType(s?: string): s is SortType {
    if (!isSortBy) return true;
    return isSortBy(s) || s === undefined;
  }
  function isOrderByType(s?: string): s is OrderByType {
    return s === "asc" || s === "desc";
  }
  const { sort: sortParam = defaultValue } = useSearchParams("sort");
  const updateSearchParams = useUpdateSearchParams<"sort" | "page">();
  let sortBy: SortType;
  let orderBy: OrderByType = "asc";
  if (sortParam) {
    const sortEntry = sortParam.split(",")[0];
    const indexOfPoint = sortEntry.indexOf(".");
    if (indexOfPoint < 0) {
      if (isSortByType(sortEntry)) {
        sortBy = sortEntry;
      }
    } else {
      const sBy = sortEntry.substring(0, indexOfPoint);
      if (isSortByType(sBy)) {
        sortBy = sBy;
        const oBy = sortEntry.substring(indexOfPoint + 1);
        if (isOrderByType(oBy)) {
          orderBy = oBy;
        }
      }
    }
  }
  const sort = sortBy ? `${sortBy}.${orderBy}` : undefined;

  const handleSortClick = (sortRule?: SortType) => {
    if (sortBy === sortRule) {
      if (orderBy === "desc") {
        updateSearchParams((params) => omit({ ...params, sort: `${sortRule}.asc` }, ["page"]), true);
      } else {
        updateSearchParams((params) => omit({ ...params, sort: `${sortRule}.desc` }, ["page"]), true);
      }
    } else {
      updateSearchParams((params) => omit({ ...params, sort: `${sortRule}.desc` }, ["page"]), true);
    }
  };

  return { sortBy, orderBy, sort, handleSortClick };
}
--------------------------------------------------------------------------
// @/utils/object.ts
export function omit<T extends Record<any, any>, U extends keyof T>(obj: T, keys: U[]): Omit<T, U> {
  const newObj = { ...obj };
  keys.forEach((key) => {
    delete newObj[key];
  });
  return newObj;
}
export function omitNil<T extends Record<any, any>>(obj: T): { [K in keyof T]: null extends T[K] ? T[K] | undefined : T[K] } {
  return Object.fromEntries(Object.entries(obj).filter(([, value]) => value != null)) as any;
}

4. 使用方式

// 引入组件
import SortButton from "@/components/SortButton";
// 使用
<SortButton field="transactions" />

5. 效果展示

在这里插入图片描述


总结

下一篇讲【全局常用组件Echarts封装】。关注本栏目,将实时更新。

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-10 04:52:04       53 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-10 04:52:04       55 阅读
  3. 在Django里面运行非项目文件

    2024-07-10 04:52:04       46 阅读
  4. Python语言-面向对象

    2024-07-10 04:52:04       56 阅读

热门阅读

  1. Mysql:时区问题

    2024-07-10 04:52:04       18 阅读
  2. WebSocket 双向通信

    2024-07-10 04:52:04       22 阅读
  3. 3102.最小化曼哈顿距离

    2024-07-10 04:52:04       24 阅读
  4. Power BI数据分析可视化实战培训

    2024-07-10 04:52:04       21 阅读
  5. Python文字数字转换利器: word2number库详解

    2024-07-10 04:52:04       29 阅读
  6. 在Spring Boot项目中使用Leyden

    2024-07-10 04:52:04       26 阅读
  7. 大模型推理:vllm多机多卡分布式本地部署

    2024-07-10 04:52:04       45 阅读
  8. 调度的艺术:Eureka在分布式资源调度中的妙用

    2024-07-10 04:52:04       27 阅读
  9. 前后端的身份认证(学习自用)

    2024-07-10 04:52:04       24 阅读
  10. 计算机网络和因特网

    2024-07-10 04:52:04       27 阅读
  11. MySQL DDL

    MySQL DDL

    2024-07-10 04:52:04      26 阅读