如何在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一致,省略重复代码 };
三、关键注意事项
- 不可变原则:排序时必须复制原数组再操作,绝对不能直接修改Redux状态中的原始数组
- 性能优化:使用
createSelector或useMemo缓存计算结果,避免每次组件渲染都重复执行排序和过滤逻辑 - RTK Query数据处理:组件只需触发数据查询,具体的数据转换、过滤、排序交给选择器或
useMemo,无需直接操作ids和entities对象
内容的提问来源于stack exchange,提问作者Aldo
相关产品推荐
相关产品推荐

