文章目录
- 前言
- SortButton组件
- 1. 功能分析
- 2. 代码+详细注释
- 3. 使用到的全局hook代码
- 4. 使用方式
- 5. 效果展示
- 总结
前言
今天要封装的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封装】。关注本栏目,将实时更新。