React中Widget重排序时缓存异步请求避免重复调用问题
问题根因
- 列表key使用不当导致组件重挂载:当前渲染Widget列表时如果默认使用数组索引作为
key,调用arrayMove调整元素顺序后,对应位置的key绑定关系错乱,React会判定对应位置是全新组件,触发旧组件卸载、新组件挂载流程,组件内部的请求副作用自然会重新执行。 useMemo使用方式错误:示例代码中useMemo的回调直接执行了fetchChartsData,返回值是Promise实例而非可调用函数,后续执行memoFetch()相当于尝试调用Promise对象,直接触发类型错误;且useMemo设计目标是缓存同步计算结果,本身不适合处理异步请求这类副作用逻辑。
解决方案
1. 为每个Widget绑定稳定唯一的key
创建Widget实例时就为其生成全局唯一、不会随位置/配置修改变更的id(可用自增数字、uuid生成),渲染列表时使用该id作为key,禁止使用数组索引作为key:
// 列表渲染逻辑 widgetsArr.forEach((item) => <Widget key={item.id} type={item.type} widgetConfig={item.config} />)
只要key值不变,即使Widget在数组中的位置发生变化,React也会复用对应的组件实例,不会触发卸载重挂载,从根源上避免排序导致的重复请求。
2. 用React.memo包裹Widget组件避免无意义重渲染
父组件状态更新触发重渲染时,默认会级联触发所有子组件重渲染。使用React.memo包裹Widget组件后,仅当组件接收的props实际发生浅比较层面的变化时,才会触发组件重渲染:
// Widget组件定义 const Widget = React.memo(({ type, widgetConfig }) => { // 组件原有业务逻辑 })
注意:如果传递给Widget的widgetConfig是父组件每次渲染时新建的对象,需要配合useMemo在父层缓存配置引用,否则浅比较会判定props变更,无法达到拦截重渲染的效果。
3. 优化组件内部请求逻辑
数据请求属于副作用,必须放在useEffect中执行,不要放在useMemo里。useEffect的依赖项仅保留实际变更后需要重新拉取数据的字段(比如数据源id、查询参数、组件类型),不要直接传入整个config对象(对象引用变更会无意义触发请求)。
正确的请求实现示例:
// Widget组件内部请求逻辑 useEffect(() => { let isMounted = true; fetchChartsData({ type, dataSourceId: widgetConfig.dataSourceId, query: widgetConfig.queryParams }).then(res => { if (isMounted) setData(res) }) return () => { isMounted = false } // 依赖仅保留影响请求的稳定值 }, [type, widgetConfig.dataSourceId, widgetConfig.queryParams])
如果需要进一步强化缓存能力,可以在父层维护一个以Widget唯一id为key的缓存Map,组件发起请求前先校验缓存,命中缓存则直接使用缓存数据,即使组件意外重挂载也不会重复发起请求。
内容的提问来源于stack exchange,提问作者Mladen Milosavljevic
相关产品推荐
相关产品推荐

