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

如何避免useSelector导致不必要的组件重渲染?

解决useSelector导致的不必要组件重渲染问题

核心原因

useSelector默认使用**严格相等(===)**对比前后返回的状态值。如果组件返回整个state对象,只要state里任何一个属性变化,组件就会重渲染;如果返回的是数组/对象这类引用类型,哪怕内容没变但引用变了,也会触发重渲染。

具体解决方案

1. 精准订阅所需状态,避免返回整个state

这是最基础也最有效的优化。不要返回整个state再解构,直接返回组件需要的单个属性:

// 错误做法:返回整个state,任何属性变化都会重渲染
const { productsDataState } = useSelector(state => state)

// 正确做法:只返回需要的productsDataState
const productsDataState = useSelector(state => state.productsDataState)

同理,使用isSearchOn的组件应该改成:

// 安全且高效的写法
const isSearchOn = useSelector(state => state.isSearchOn)

这样只有当productsDataState或isSearchOn本身变化时,组件才会触发重渲染。

2. 处理引用类型的相等性对比

如果productsDataState是数组/对象,即使内容没变,但reducer中返回了新的引用(比如每次更新都是全新数组),useSelector会认为状态变化。这时候可以用自定义对比函数:

  • 浅对比:使用react-redux内置的shallowEqual,适合数组元素是对象但只关心引用变化的场景:
    import { shallowEqual, useSelector } from 'react-redux'
    
    const productsDataState = useSelector(
      state => state.productsDataState,
      shallowEqual
    )
    
  • 深度对比:如果需要对比数组/对象的内容而非引用,可以用lodash的isEqual:
    import { useSelector } from 'react-redux'
    import isEqual from 'lodash/isEqual'
    
    const productsDataState = useSelector(
      state => state.productsDataState,
      isEqual
    )
    
    注意:深度对比有一定性能开销,但对于数据量不大的场景完全可以接受。

3. 优化Reducer的状态更新逻辑

尽量避免生成不必要的新引用,比如在更新productsDataState时,只修改需要变更的元素而非返回全新数组:

// 示例:只更新数组中某个产品的属性,而非替换整个数组
case actionTypes.UPDATE_PRODUCT_ITEM:
  return {
    ...state,
    productsDataState: state.productsDataState.map(item => 
      item.id === action.id ? {...item, ...action.updates} : item
    )
  }

如果觉得手写不可变更新麻烦,可以用immer库简化,它会自动生成不可变的新状态,同时只修改变化的部分:

import { produce } from 'immer'

const reducer = produce((state, action) => {
  switch (action.type) {
    case actionTypes.UPDATE_PRODUCT:
      state.productsDataState = action.products
      state.listOrder = action.listOrder
      break
    case actionTypes.UPDATE_PRODUCT_ITEM:
      const item = state.productsDataState.find(i => i.id === action.id)
      if (item) Object.assign(item, action.updates)
      break
    // 其他case同理
  }
}, initialState)

4. 拆分Reducer,按功能模块化

把大的单一Reducer拆分成多个小Reducer,比如按产品、搜索、提示框功能拆分,用combineReducers合并:

import { combineReducers } from 'redux'

const productsReducer = (state = { productsDataState: [], listOrder: [] }, action) => {
  switch (action.type) {
    case actionTypes.UPDATE_PRODUCT:
      return { ...state, productsDataState: action.products, listOrder: action.listOrder }
    default:
      return state
  }
}

const searchReducer = (state = { searchProducts: [], isSearchOn: false, inputValue: '' }, action) => {
  switch (action.type) {
    case actionTypes.SET_SEARCH:
      return { ...state, ...action.payload }
    default:
      return state
  }
}

const toastifyReducer = (state = { toastify: ['green', ''], toastifyOpen: false }, action) => {
  switch (action.type) {
    case actionTypes.SET_TOASTIFY:
      return { ...state, toastify: action.toastify, toastifyOpen: action.open }
    default:
      return state
  }
}

const rootReducer = combineReducers({
  products: productsReducer,
  search: searchReducer,
  toastify: toastifyReducer
})

之后组件订阅时只关注对应模块的状态:

const productsDataState = useSelector(state => state.products.productsDataState)

这样其他模块(比如搜索、提示框)的状态变化不会影响该组件的重渲染。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 21:15:46