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

如何在PERN bug追踪应用中用RTK Query与createEntityAdapter实现排序过滤

解决方案:RTK Query + createEntityAdapter 实现前端排序与过滤

一、修复自定义选择器的核心问题

你的selectSortedTickets存在两个关键缺陷:直接修改原数组(违反Redux不可变状态原则)、未返回排序后的结果。修复后的代码如下:

export const selectSortedTickets = createSelector(
  [selectAllTickets, (state, sortArg) => sortArg],
  (tickets, sortArg) => {
    // 复制原数组,避免修改Redux中的原始状态
    const sortedTickets = [...tickets];
    
    switch(sortArg) {
      case "Priority":
        sortedTickets.sort((a, b) => b.priority - a.priority);
        break;
      case "Severity":
        sortedTickets.sort((a, b) => b.severity - a.severity);
        break;
      case "Application":
        sortedTickets.sort((a, b) => {
          const nameA = a.application.toUpperCase();
          const nameB = b.application.toUpperCase();
          return nameA.localeCompare(nameB);
        });
        break;
      case "Nature":
        sortedTickets.sort((a, b) => {
          const nameA = a.nature.toUpperCase();
          const nameB = b.nature.toUpperCase();
          return nameA.localeCompare(nameB);
        });
        break;
      case "Assignee":
        sortedTickets.sort((a, b) => b.userId - a.userId);
        break;
      default:
        sortedTickets.sort((a, b) => b.status - a.status);
    }
    // 返回排序后的新数组
    return sortedTickets;
  }
);

二、在组件中结合过滤与排序

在SingleUserTicketsList中,需要先过滤出当前用户的工单,再应用排序。以下提供两种可行方案:

方案1:使用Redux选择器组合逻辑

先创建一个同时处理过滤和排序的组合选择器:

// 在ticketsApiSlice.js中新增
export const selectFilteredAndSortedTickets = createSelector(
  [selectSortedTickets, (state, userId, userName) => ({ userId, userName })],
  (sortedTickets, { userId, userName }) => {
    return sortedTickets.filter(ticket => 
      ticket.userId === userId || ticket.author === userName
    );
  }
);

然后在组件中使用:

// SingleUserTicketsList.js
import { useSelector, useState } from 'react-redux';
import { selectFilteredAndSortedTickets } from './ticketsApiSlice';

const SingleUserTicketsList = () => {
  const { id } = useParams();
  const userId = Number(id);
  const [sortBy, setSortBy] = useState('Status'); // 默认排序字段

  // 获取当前用户名
  const { user: userName } = useGetUsersQuery("usersList", {
    selectFromResult: ({ data }) => ({
      user: data?.entities[id].username,
    }),
  });

  // 触发工单数据查询,无需直接处理返回的data
  const { isLoading, isError, error } = useGetTicketsQuery("ticketsList", {
    pollingInterval: 15000,
    refetchOnFocus: true,
    refetchOnMountOrArgChange: true,
  });

  // 通过选择器获取过滤并排序后的工单
  const filteredSortedTickets = useSelector(state => 
    selectFilteredAndSortedTickets(state, userId, userName, sortBy)
  );

  let content;

  if (isLoading) content = <PulseLoader color={"#FFF"} />;
  if (isError) content = <p className="errmsg">{error?.data?.message}</p>;

  if (userName && filteredSortedTickets.length) {
    const tableContent = filteredSortedTickets.map(ticket => (
      <tr key={ticket.id} className="table__row">
        <td className="table__cell">
          <Link to={`/dash/tickets/${ticket.id}`}>{ticket.name}</Link>
        </td>
        <td className="table__cell">{ticket.application}</td>
        <td className="table__cell ticket__status">
          {ticket.status ? (
            <span className="ticket__status--completed">Closed</span>
          ) : (
            <span className="ticket__status--open">Open</span>
          )}
        </td>
        <td className="table__cell ticket__priority">
          {priorityName(ticket.priority)}
        </td>
        <td className="table__cell ticket__severity">
          {severityName(ticket.severity)}
        </td>
        <td className="table__cell ticket__nature">{ticket.nature}</td>
      </tr>
    ));

    content = (
      <table className="table__userbugs">
        <thead className="table__thead">
          <tr>
            <th scope="col" className="table__th" onClick={() => setSortBy('Status')}>
              {userName}'s Bugs
            </th>
            <th scope="col" className="table__th ticket__app" onClick={() => setSortBy('Application')}>
              App
            </th>
            <th scope="col" className="table__th ticket__status" onClick={() => setSortBy('Status')}>
              Status
            </th>
            <th scope="col" className="table__th ticket__priority" onClick={() => setSortBy('Priority')}>
              Priority
            </th>
            <th scope="col" className="table__th ticket__severity" onClick={() => setSortBy('Severity')}>
              Severity
            </th>
            <th scope="col" className="table__th ticket__nature" onClick={() => setSortBy('Nature')}>
              Nature
            </th>
          </tr>
        </thead>
        <tbody>{tableContent}</tbody>
      </table>
    );
  } else if (userName) {
    content = <p className="info">No tickets found for this user.</p>;
  }

  return content;
};

方案2:组件内使用useMemo处理逻辑

如果不想新增选择器,可以直接在组件内用useMemo缓存过滤和排序结果:

// SingleUserTicketsList.js
import { useMemo, useState } from 'react';

const SingleUserTicketsList = () => {
  const { id } = useParams();
  const userId = Number(id);
  const [sortBy, setSortBy] = useState('Status');

  const { user: userName } = useGetUsersQuery("usersList", {
    selectFromResult: ({ data }) => ({
      user: data?.entities[id].username,
    }),
  });

  const {
    data: tickets,
    isLoading,
    isSuccess,
    isError,
    error,
  } = useGetTicketsQuery("ticketsList", {
    pollingInterval: 15000,
    refetchOnFocus: true,
    refetchOnMountOrArgChange: true,
  });

  // 缓存过滤和排序后的结果
  const filteredSortedTickets = useMemo(() => {
    if (!tickets?.entities) return [];

    // 过滤当前用户的工单
    const userTickets = Object.values(tickets.entities).filter(ticket => 
      ticket.userId === userId || ticket.author === userName
    );

    // 排序处理
    const sortedTickets = [...userTickets];
    switch(sortBy) {
      case "Priority":
        sortedTickets.sort((a, b) => b.priority - a.priority);
        break;
      case "Severity":
        sortedTickets.sort((a, b) => b.severity - a.severity);
        break;
      case "Application":
        sortedTickets.sort((a, b) => a.application.toUpperCase().localeCompare(b.application.toUpperCase()));
        break;
      case "Nature":
        sortedTickets.sort((a, b) => a.nature.toUpperCase().localeCompare(b.nature.toUpperCase()));
        break;
      case "Assignee":
        sortedTickets.sort((a, b) => b.userId - a.userId);
        break;
      default:
        sortedTickets.sort((a, b) => b.status - a.status);
    }
    return sortedTickets;
  }, [tickets, userId, userName, sortBy]);

  // 后续内容和方案1一致,省略重复代码
};

三、关键注意事项

  1. 不可变原则:排序时必须复制原数组再操作,绝对不能直接修改Redux状态中的原始数组
  2. 性能优化:使用createSelector或useMemo缓存计算结果,避免每次组件渲染都重复执行排序和过滤逻辑
  3. RTK Query数据处理:组件只需触发数据查询,具体的数据转换、过滤、排序交给选择器或useMemo,无需直接操作ids和entities对象

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 23:57:08