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

React/Next.js/Redux应用中可复用数据加载组件的实现方案咨询

React/Next.js/Redux应用中可复用数据加载组件的实现方案咨询

嘿,我完全懂你的困扰——每个组件都要重复写一遍数据加载、状态判断的逻辑,时间久了不仅冗余还容易出错😣。咱们可以通过封装通用数据加载Hook或者通用数据加载组件来彻底消除这些重复代码,下面给你具体的思路和示例:

方案一:封装通用数据加载Hook

先把你现有的useSelectorEffect扩展成更通用的版本,把重复的请求触发、状态判断逻辑都抽离进去:

import { useEffect } from 'react';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { StateLoading } from "@/shared/constants/loading";

// 定义Hook的参数类型
type DataLoaderOptions<T> = {
  dataSelector: (state: RootState) => T;
  statusSelector: (state: RootState) => StateLoading;
  fetchAction: () => void;
  dependencies?: React.DependencyList;
};

export const useDataLoader = <T>({
  dataSelector,
  statusSelector,
  fetchAction,
  dependencies = [],
}: DataLoaderOptions<T>) => {
  const dispatch = useAppDispatch();
  const data = useAppSelector(dataSelector);
  const status = useAppSelector(statusSelector);
  
  // 自动触发请求的逻辑:数据为空且非加载状态时发起请求
  useEffect(() => {
    if (!data && status !== StateLoading.LOADING) {
      dispatch(fetchAction());
    }
  }, [dispatch, data, status, fetchAction, ...dependencies]);

  // 返回统一的状态和数据
  return {
    data,
    isLoading: status === StateLoading.LOADING,
    isClientLoaded: !!data,
  };
};

之后你的GamesDisplay组件就能简化成这样:

import { useDataLoader } from '@/hooks/useDataLoader';
import { getGames, selectGames, selectStatus } from "@/store/games/gamesSlice";
import { Game } from "@/shared/interfaces/game";

const GamesDisplay = () => {
  const { data: games, isLoading, isClientLoaded } = useDataLoader<Game[]>({
    dataSelector: selectGames,
    statusSelector: selectStatus,
    fetchAction: getGames,
  });

  if (isLoading) {
    return <div>Loading....</div>;
  }
  
  return (
    <>
      <div>Games Display</div>
      {isClientLoaded && (
        <ul>
          {games?.map((game) => (
            <li key={game.id}>{game.name}</li>
          ))}
        </ul>
      )}
    </>
  );
};

方案二:封装通用数据加载组件

如果连加载状态的UI(比如Loading提示、空数据提示)都重复,还可以把UI和数据逻辑一起封装成通用组件:

import { ReactNode } from 'react';
import { useDataLoader } from '@/hooks/useDataLoader';
import { StateLoading } from "@/shared/constants/loading";

type DataLoaderProps<T> = {
  dataSelector: (state: RootState) => T;
  statusSelector: (state: RootState) => StateLoading;
  fetchAction: () => void;
  dependencies?: React.DependencyList;
  loadingUI?: ReactNode; // 自定义加载UI
  emptyUI?: ReactNode; // 自定义空数据UI
  children: (data: T, isClientLoaded: boolean) => ReactNode;
};

export const DataLoader = <T>({
  dataSelector,
  statusSelector,
  fetchAction,
  dependencies = [],
  loadingUI = <div>Loading....</div>,
  emptyUI = <div>No data available</div>,
  children,
}: DataLoaderProps<T>) => {
  const { data, isLoading, isClientLoaded } = useDataLoader({
    dataSelector,
    statusSelector,
    fetchAction,
    dependencies,
  });

  if (isLoading) {
    return loadingUI;
  }

  if (!isClientLoaded) {
    return emptyUI;
  }

  return children(data, isClientLoaded);
};

用这个组件渲染GamesDisplay会更简洁:

import { DataLoader } from '@/components/DataLoader';
import { getGames, selectGames, selectStatus } from "@/store/games/gamesSlice";
import { Game } from "@/shared/interfaces/game";

const GamesDisplay = () => {
  return (
    <DataLoader<Game[]>
      dataSelector={selectGames}
      statusSelector={selectStatus}
      fetchAction={getGames}
    >
      {(games) => (
        <>
          <div>Games Display</div>
          <ul>
            {games?.map((game) => (
              <li key={game.id}>{game.name}</li>
            ))}
          </ul>
        </>
      )}
    </DataLoader>
  );
};

这样不管是后续的用户列表、订单列表组件,都能直接复用这两个封装后的工具,再也不用重复写相同的加载逻辑啦!

备注:内容来源于stack exchange,提问作者RasMason

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.16 09:05:28