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

React中useSelector与useEffect导致表格重复重渲染的优化问题

问题解决方案

根因分析

当前两次重渲染的原因非常明确:

  1. 全局store中的searcher更新后,useSelector检测到返回值变化,触发CatalogTable第一次重渲染
  2. 重渲染完成后useEffect检测到依赖项searcher变更,执行getData请求接口,请求完成后setTableRows更新状态,触发第二次重渲染

解决方案

方案一:移除顶层useSelector,通过手动订阅store避免searcher变更触发渲染

原理:useStore拿到的store实例本身是稳定引用,不会触发组件重渲染,我们手动订阅store变化,仅当searcher真正变化时才发起请求更新tableRows,全程searcher变更不会主动触发组件重渲染,只有setTableRows时才会触发一次渲染。

import { useStore } from 'react-redux'
import { shallowEqual } from 'react-redux'

const CatalogTable: React.FC<CatalogTableProps> = ({rows}) => {
    const [tableRows, setTableRows] = React.useState(rows)
    const store = useStore()
    const searcherRef = React.useRef(getTableSearcher(store.getState()))

    const getData = async (currentSearcher) => {
        const {data} = await CatalogService.getAllCatalogProducts({page: 1, searcher: currentSearcher})
        setTableRows(data.products)
    }

    React.useEffect(() => {
        // 订阅store变化,手动比较searcher值
        const unsubscribe = store.subscribe(() => {
            const latestSearcher = getTableSearcher(store.getState())
            // 如果searcher是引用类型,这里可以替换为shallowEqual判断
            if (latestSearcher && !shallowEqual(latestSearcher, searcherRef.current)) {
                searcherRef.current = latestSearcher
                getData(latestSearcher)
            }
        })
        // 组件卸载时取消订阅
        return unsubscribe
    }, [store])

    return (
        <>
            <div className={styles.defaultTable}>
                <Table
                    headers={headers}
                    label="Products catalog"
                    rows={tableRows}
                    total={total}
                    pagination
                    addButton
                    editButtons
                    searcher
                    getPage={(page: number) => nextPage(page)}
                    type='catalog'
                />
            </div>
        </>
    )
}

export default CatalogTable

方案二:组件拆分+React.memo隔离渲染

原理:把组件拆分为逻辑层和视图层,逻辑层负责处理搜索监听和数据请求,视图层用memo包裹,仅当传入的tableRows等必要props变化时才触发渲染,逻辑层的多余重渲染不会传递到视图层。

// 视图层组件,仅props变化时重渲染
const CatalogTableView = React.memo(({ tableRows, total, nextPage, headers }) => {
  return (
    <div className={styles.defaultTable}>
      <Table
          headers={headers}
          label="Products catalog"
          rows={tableRows}
          total={total}
          pagination
          addButton
          editButtons
          searcher
          getPage={nextPage}
          type='catalog'
      />
    </div>
  )
})

// 逻辑层组件,处理搜索监听和数据请求
const CatalogTable: React.FC<CatalogTableProps> = ({rows}) => {
    const [tableRows, setTableRows] = React.useState(rows)
    const searcher = useSelector(getTableSearcher, shallowEqual)
    // 用useCallback保证nextPage引用稳定,避免memo失效
    const nextPage = useCallback((page: number) => {
      // 原nextPage逻辑
    }, [])

    const getData = async () => {
        const {data} = await CatalogService.getAllCatalogProducts({page: 1, searcher: searcher})
        setTableRows(data.products)
    }

    React.useEffect(() => {
        if(searcher)
            getData()
    }, [searcher])

    return <CatalogTableView 
      tableRows={tableRows}
      total={total}
      nextPage={nextPage}
      headers={headers}
    />
}

export default CatalogTable

注意:如果你的Table组件需要接收实时的searcher参数,方案二会因为searcher变化触发视图层重渲染,这种场景优先选择方案一。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 21:06:03