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

React Redux中如何避免多次重复使用useSelector Hook?

问题修复方案

一、useSelector 使用优化

首先明确:多次调用useSelector本身是React Redux官方推荐的合法写法,不存在性能问题,只要单个选择器返回基本类型值、或者返回记忆化的引用,就不会触发不必要的重渲染,不用刻意追求必须合并所有选择逻辑。如果同模块的关联状态较多,也可以合并选择,搭配浅比较避免重渲染:

import { shallowEqual, useSelector } from 'react-redux';

// 同模块关联状态合并选择示例
const { plantsList, isFetching, currentPage, totalCount, limitItems } = useSelector(
  state => ({
    plantsList: state.catalog.plantsList,
    isFetching: state.catalog.isFetching,
    currentPage: state.catalog.currentPage,
    totalCount: state.catalog.totalCount,
    limitItems: state.catalog.limitItems
  }),
  shallowEqual // 必须加,否则每次返回新对象会触发多余重渲染
);

如果后续有pagesCount这类需要重复计算的派生状态,可以搭配reselect库创建记忆化选择器,把计算逻辑从组件内抽离,避免每次渲染都重复计算。

二、业务逻辑与UI解耦方案

当前组件同时承担了状态读取、数据请求、分页逻辑计算、列表渲染、分页交互5类职责,按职责拆分即可:

1. 抽离业务逻辑到自定义Hook

把所有和Catalog业务相关的逻辑抽成独立的useCatalog自定义Hook,组件只需要调用Hook拿到渲染需要的数据和回调即可,不用关心状态来源、请求触发时机这类业务细节:

// 单独存为 hooks/useCatalog.js
import { useEffect } from 'react';
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
import { fetchData, setCurrentPage } from '@/store/catalogSlice';

const useCatalog = () => {
  const dispatch = useDispatch();
  const { plantsList, isFetching, currentPage, totalCount, limitItems } = useSelector(
    state => ({
      plantsList: state.catalog.plantsList,
      isFetching: state.catalog.isFetching,
      currentPage: state.catalog.currentPage,
      totalCount: state.catalog.totalCount,
      limitItems: state.catalog.limitItems
    }),
    shallowEqual
  );

  // 派生状态计算
  const pagesCount = Math.ceil(totalCount / limitItems);
  const pages = [];
  createPages(pages, pagesCount, currentPage);

  // 数据请求逻辑,补全原代码缺失的依赖项
  useEffect(() => {
    dispatch(fetchData(currentPage, limitItems));
  }, [currentPage, limitItems, dispatch]);

  // 交互回调封装
  const handlePageChange = (page) => {
    dispatch(setCurrentPage(page));
  };

  return {
    plantsList,
    isFetching,
    currentPage,
    pages,
    handlePageChange
  };
};

export default useCatalog;

2. 拆分纯展示组件

把和业务逻辑无关的UI渲染逻辑拆成无状态组件,只接收props做渲染,不直接依赖Redux:

// 植物列表纯组件
const PlantList = ({ isFetching, items }) => {
  if (isFetching) {
    return <div className={s.fetching}><Preloader/></div>
  }
  return (
    <div className={s.catalog__list}>
      {items.map(p => <PlantItem key={p.id} id={p.id} image={p.image} name={p.name} cost={p.cost}/>)}
    </div>
  )
};

// 分页器纯组件
const Pagination = ({ pages, currentPage, onPageChange }) => {
  return (
    <div className={s.catalog__pages}>
      {pages.map((page, index) => 
        <div
          key={index}
          className={currentPage == page ? s.currentPage : s.page}
          onClick={() => onPageChange(page)}
        >{page}</div>
      )}
    </div>
  )
};

3. 重构后的Catalog组件仅做组装

最终的Catalog组件只负责拼接逻辑和UI,代码非常简洁,后续改业务逻辑只需要改Hook,改UI只需要改对应纯组件:

const Catalog = () => {
  const { plantsList, isFetching, currentPage, pages, handlePageChange } = useCatalog();

  return (
    <div className={s.catalog}>
      <p className={s.catalog__description}>Сдавая пластик в пункты приема, Вы получаете природные баллы. За один килограмм пластика можно получить 10 природных баллов.</p>
      <PlantList isFetching={isFetching} items={plantsList} />
      <Pagination pages={pages} currentPage={currentPage} onPageChange={handlePageChange} />
    </div>
  );
};
学习资料推荐
  • 文档类:优先读React官方文档的自定义Hook、状态逻辑复用章节,再读React Redux官方文档的useSelector性能优化、选择器编写章节,内容准确性最高。
  • 实践类:可以搜索React容器组件与展示组件拆分、自定义Hook抽离业务逻辑、Redux状态分层设计相关的实战教程,搭配reselect官方文档学习记忆化选择器的用法。
  • 视频类:找React组件解耦实战、Redux最佳实践相关的免费教程即可,重点学逻辑拆分思路,不用纠结具体API写法。

拆分核心原则:和UI渲染无关的业务逻辑不要直接写在组件函数体里,纯展示组件不要直接依赖全局状态和dispatch方法。按这个原则拆分后,代码可维护性、可测试性都会明显提升。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 00:06:13