如何基于RTK Query数据生成派生状态?方案选型与缓存访问咨询
我正在开发一个追踪徒步者山峰登顶清单进度的小型应用,首次使用RTK Query进行主要状态管理。需要生成一个名为list counts的派生状态对象,结构为山峰清单ID与对应已完成登顶山峰数量的键值对:
const listCounts = { nh4k: 6, ne100: 5, usHigh: 3, // more data }
计算该派生状态需要两个RTK Query钩子返回的数据,且该派生数据需在多个组件中使用。目前有两种可行方案:
方案一:自定义Hook
编写自定义Hook,调用两个RTK Query生成的钩子并返回listCounts:
const useListCounts = () => { const { data: peakLists } = useGetPeakListsQuery(); const { data: logEntries } = useGetLogEntriesQuery(); // logic to create listCounts object return listCounts; }
疑问:这种自定义Hook的使用是否恰当?我对自定义Hook经验不多,所见示例大多返回useState、useReducer等React内置钩子的结果。
方案二:独立工具函数
编写独立工具函数,在组件中直接调用RTK Query钩子后传入数据进行计算:
// in util function file: const calculateListCounts = (peakLists, logEntries) => { // logic to create listCounts object return listCounts; } // In component file: import { calculateListCounts } from "..." const MyComponent = () => { const { data: peakLists } = useGetPeakListsQuery(); const { data: logEntries } = useGetLogEntriesQuery(); const listCounts = calculateListCounts(peakLists, logEntries); // return JSX }
疑问:是否有理由偏好其中一种方案?或者是否可以在React组件或自定义Hook外访问RTK Query缓存,从而通过单次函数调用得到listCounts?
此外,还有多个类似场景,需基于RTK Query钩子返回的数据生成派生状态并在多组件中使用,希望遵循正确模式。
1. 自定义Hook方案的合理性
你的自定义Hook方案完全恰当,自定义Hook的核心价值就是复用组件逻辑,并不局限于返回内置Hook的结果。只要Hook内部调用了React内置Hook(包括RTK Query钩子,本质也是基于React Hook实现的),就符合自定义Hook的规则。
这个方案的优势:
- 逻辑复用彻底:所有和获取
listCounts相关的逻辑(调用RTK Query钩子、数据计算)都封装在Hook里,组件只需调用useListCounts()就能拿到结果,无需重复写调用RTK Query的代码。 - 自动同步状态:RTK Query的钩子会自动处理缓存、重新获取数据等逻辑,自定义Hook会继承这些特性——当
peakLists或logEntries的缓存更新时,listCounts会自动重新计算并触发组件重渲染。
2. 独立工具函数方案的适用场景
工具函数方案也是可行的,但它更适合计算逻辑需要单独复用,且不同组件获取数据源的方式可能不同的场景。比如某些组件可能不是通过RTK Query钩子获取peakLists和logEntries,而是从props或其他状态中拿到数据,这时工具函数就能单独复用计算逻辑。
但如果所有需要listCounts的组件都是通过相同的RTK Query钩子获取数据,那么工具函数方案会导致每个组件都要重复写调用两个RTK Query钩子的代码,冗余度更高。
3. 能否在组件/Hook外访问RTK Query缓存?
可以,但不推荐直接这么做。RTK Query的缓存存储在Redux store中,你可以通过store.getState()直接访问缓存数据,但这种方式脱离了React的状态更新机制:
- 无法自动监听缓存变化,当缓存更新时,外部函数不会自动重新计算
listCounts,组件也不会触发重渲染。 - 违反了React的数据流模式,容易导致状态不同步的问题。
如果需要在非组件环境(比如Redux middleware、工具脚本)中获取缓存数据,可以这么做,但在组件中,还是应该通过RTK Query钩子或自定义Hook来获取数据,保证状态的一致性和自动更新。
4. 多场景下的最佳实践
对于多个类似的派生状态场景,推荐遵循以下模式:
- 封装自定义Hook:如果某个派生状态的数据源固定(比如都是通过特定的RTK Query钩子获取),就把数据源调用和计算逻辑封装成自定义Hook,最大化复用逻辑。
- 拆分计算逻辑:把纯计算逻辑抽成独立的工具函数,在自定义Hook内部调用这些工具函数。这样既保证了组件端的简洁,又能让计算逻辑在其他场景下复用。
示例优化代码:
// 纯计算工具函数 const calculateListCounts = (peakLists, logEntries) => { // 计算逻辑 return listCounts; } // 自定义Hook const useListCounts = () => { const { data: peakLists } = useGetPeakListsQuery(); const { data: logEntries } = useGetLogEntriesQuery(); // 使用useMemo避免不必要的重复计算 const listCounts = useMemo(() => { if (!peakLists || !logEntries) return {}; return calculateListCounts(peakLists, logEntries); }, [peakLists, logEntries]); return listCounts; }
这里加上useMemo可以优化性能,避免每次组件重渲染都重复计算listCounts,只有当peakLists或logEntries变化时才重新计算。
内容的提问来源于stack exchange,提问作者Kris

