You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+TS+RTK Query中搜索排序工具函数未按预期工作排查

排序异常根因
  • 核心逻辑漏洞:String.indexOf() 方法在字符串不包含目标子串时会返回 -1,这个值比所有合法匹配位置(0、1、2……)数值更小。原排序规则是按匹配位置升序排列,导致所有不包含搜索词的卡牌反而被排在结果最前面,这就是前3条出现无关卡牌的直接原因。
  • 排序比较器不符合规范:当两个卡牌名称字母序完全相等时,原代码直接返回 -1,不符合 Array.sort() 比较器的返回值要求(值相等时应返回0),会导致排序结果不稳定,出现预期外的乱序。
  • 存在冗余计算:排序比较过程中重复对相同字符串执行 toLowerCase() 和 indexOf() 计算,存在不必要的性能损耗。
  • 边界场景缺失:未处理搜索词为空、接口返回数据结构异常的兜底逻辑,容易触发运行时报错。
修复实现

修复后的排序工具函数

import { CardData } from "../models/cardData.interface";

const SortNameBySearch = (search: string, cardData: CardData) => {
  // 异常场景兜底
  if (!cardData?.data || !search.trim()) {
    return cardData?.data ?? [];
  }
  const keyword = search.toLowerCase();
  const sortedData = [...cardData.data];
  
  sortedData.sort((a, b) => {
    const nameA = a.name.toLowerCase();
    const nameB = b.name.toLowerCase();
    const posA = nameA.indexOf(keyword);
    const posB = nameB.indexOf(keyword);

    // 不匹配搜索词的项统一放到列表末尾
    if (posA === -1 && posB !== -1) return 1;
    if (posB === -1 && posA !== -1) return -1;
    // 匹配项按搜索词出现位置升序排列
    if (posA !== posB) return posA - posB;
    // 位置相同时按名称字母序排序,名称完全相等返回0保证排序稳定
    return nameA.localeCompare(nameB);
  });

  return sortedData;
};

export default SortNameBySearch;

组件侧优化代码

import { FC, useEffect, useMemo, useRef, useState } from "react";
import { useGetCardsBySearchQuery } from "../../../../services/api";
import { SearchResultsProps } from "./SearchResults.types";
import SortNameBySearch from "../../../../utils/SortNamesBySearch";
import { Card } from "../../../../models/card.interface";

const SearchResults: FC<SearchResultsProps> = ({ search }) => {
  const [searched, setSearched] = useState("");
  const searchRef = useRef<number | null>(null);
  
  useEffect(() => {
    if (searchRef.current) clearTimeout(searchRef.current);
    // 空搜索词清空已搜索值,不发起请求
    if (!search?.trim()) {
      setSearched("");
      return;
    }
    searchRef.current = window.setTimeout(() => {
      setSearched(search.trim());
    }, 100);
    // 组件卸载时清理定时器,避免内存泄漏
    return () => {
      if (searchRef.current) clearTimeout(searchRef.current);
    }
  }, [search]);

  const { data, isLoading, isError, isSuccess } = useGetCardsBySearchQuery(
    searched,
    { skip: !searched }
  );

  // 缓存排序结果,仅当依赖项变化时重新计算,避免重复排序
  const displayResults = useMemo(() => {
    if (!isSuccess || !data) return [];
    return SortNameBySearch(searched, data).slice(0, 3);
  }, [data, isSuccess, searched]);

  if (isError) {
    return <div>No card matching your query was found in the database.</div>;
  }

  if (isLoading || search !== searched) {
    return <>loading...</>;
  }

  return (
    <div className="bg-black relative">
      {displayResults.map((card: Card) => (
        <p key={card.id} className="text-white">
          {card.name}
        </p>
      ))}
    </div>
  );
};

export default SearchResults;
额外优化建议
  • 可以扩展排序权重规则,比如和搜索词完全匹配的卡牌优先级最高,搜索词在名称开头的次之,再按出现位置靠后程度降权,搜索体验会更符合预期。
  • 如果后续接口返回的卡牌量级变大,可以在排序前先过滤掉完全不包含搜索词的项,减少排序阶段的计算量。
  • 当前使用的防抖逻辑和RTK Query的缓存机制没有冲突,不需要额外调整缓存配置。

内容的提问来源于stack exchange,提问作者Benji

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 07:12:27