使用usePrevious与Redux时如何避免React组件出现额外重渲染?
问题根因
你的猜测是对的,两次独立的dispatch是核心问题:Redux默认每次dispatch都会触发所有订阅的组件执行useSelector校验,两次dispatch会导致组件出现两次重渲染的中间态:
- 第一次dispatch更新messages,此时oldestMessage还未更新,usePrevious拿到的还是上一次的oldest值,和当前未更新的oldestMessage相等,错误触发
setShowGetMore(false) - 第二次dispatch更新oldestMessage时,usePrevious已经拿到了第一次重渲染后的oldest值,和新值对比又会触发一次判断,逻辑完全混乱。
另外selectMessages出现多余重渲染的原因大概率是你没有用带缓存的selector,每次执行selectMessages都返回了新的数组引用,哪怕内容和之前完全一致,shallowEqual对比引用不同也会触发重渲染。
优化方案
1. 合并关联状态的更新(最优先)
更新messages和更新oldestMessage本身是关联的业务操作,完全可以合并为同一个action,在reducer里同时更新两个字段,一次dispatch就能完成所有状态变更,只会触发一次组件重渲染,从根源上消除中间态问题。
2. 开启Redux批处理
如果确实需要拆分两个action,可通过批处理合并重渲染:
- 如果你用的是Redux Toolkit,默认已经开启自动批处理,同一个同步调用栈内的多次dispatch只会触发一次重渲染
- 老版本Redux可以用
react-dom的unstable_batchedUpdates包裹两次dispatch:
import { unstable_batchedUpdates } from 'react-dom' // 拉取消息成功后的回调 unstable_batchedUpdates(() => { dispatch(updateMessages(newMessages)) dispatch(updateOldestMessage(newOldest)) })
3. 合并selector减少重渲染
把两个独立的selector合并为一个,用shallowEqual对比返回的对象,只有messages或者oldestMessage实际变化时才会触发重渲染:
const { messages, oldestMessage } = useSelector( (store) => ({ messages: selectMessages(store, chatId), oldestMessage: selectOldestMessage(store, chatId) }), shallowEqual )
同时建议用带缓存的selector实现(比如reselect库),只要store中关联的状态没有变化,就直接返回之前缓存的数组引用,避免selectMessages的多余重渲染。
4. 替换usePrevious为ref存储对比基准
usePrevious依赖组件重渲染更新值,很容易受多余重渲染影响,可以用ref单独存储上一次拉取成功的最早时间戳,只有拉取到新的历史消息时才更新ref,判断逻辑更稳定:
const lastOldestTimestampRef = useRef(null) // 拉取更多消息成功的回调 const handleGetMoreSuccess = (newMessages, newOldest) => { if (newOldest === lastOldestTimestampRef.current) { setShowGetMore(false) return } lastOldestTimestampRef.current = newOldest // 后续dispatch更新store逻辑 }
内容的提问来源于stack exchange,提问作者darthzeran
相关产品推荐
相关产品推荐

