使用Redux + 自定义Hook

准备 Loader

Loader.tsx 文件

import React from 'react';  
import styled from 'styled-components';  
  
const Loader = () => {  
	return (  
	<StyledWrapper>  
		<span className="loader" />  
	</StyledWrapper>  
	);  
}  
import React from 'react';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import Loader from './loader';
import { selectLoading } from '@/store/features/loader/loaderSlice';

const GlobalLoader = () => {
  // 从Redux状态中获取loading状态
  const isLoading = useSelector(selectLoading);

  // 如果不在加载状态,则不渲染任何内容
  if (!isLoading) return null;

  return (
    <LoaderOverlay>
      <Loader />
    </LoaderOverlay>
  );
};

const LoaderOverlay = styled.div`
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(0, 0, 0, 0.1);
  z-index: 9999; // 确保Loader显示在所有内容之上
`;

export default GlobalLoader;

use——loader.tsx

import { useDispatch } from 'react-redux';
import { showLoader, hideLoader } from '@/store/features/loader/loaderSlice';

/**
 * 自定义Hook,用于控制全局加载状态
 * @returns 包含显示和隐藏加载器的方法的对象
 */
export const useLoader = () => {
  const dispatch = useDispatch();

  // 显示全局加载器
  const showGlobalLoader = () => {
    dispatch(showLoader());
  };

  // 隐藏全局加载器
  const hideGlobalLoader = () => {
    dispatch(hideLoader());
  };

  return {
    showGlobalLoader,
    hideGlobalLoader,
  };
};

全局Loader

global-loader.tsx

import React from 'react';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import Loader from './loader';
import { selectLoading } from '@/store/features/loader/loaderSlice';

const GlobalLoader = () => {
  // 从Redux状态中获取loading状态
  const isLoading = useSelector(selectLoading);

  // 如果不在加载状态,则不渲染任何内容
  if (!isLoading) return null;

  return (
    <LoaderOverlay>
      <Loader />
    </LoaderOverlay>
  );
};

const LoaderOverlay = styled.div`
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(0, 0, 0, 0.1);
  z-index: 9999; // 确保Loader显示在所有内容之上
`;

export default GlobalLoader;

提供全局加载状态的UI展示

这个组件应该被放置在应用的根组件中,以确保加载状态可以在整个应用中显示

loader-provider.tsx

import React from 'react';
import GlobalLoader from './global-loader';

/**
 * LoaderProvider组件
 * 提供全局加载状态的UI展示
 * 这个组件应该被放置在应用的根组件中,以确保加载状态可以在整个应用中显示
 */
export const LoaderProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
  return (
    <>
      <GlobalLoader />
      {children}
    </>
  );
};

根组件加载

export default function Providers({children}: { children: React.ReactNode }) {
  return (
    <ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
      <Provider store={store}>
        <LoaderProvider>
          <App/>
          {children}
          <RouterProvider router={router}/>
          <Toaster/>
        </LoaderProvider>
      </Provider>
    </ThemeProvider>
  );
}

实例

import React from 'react';
import { Button } from '@/components/ui/button';
import { useLoader } from '@/hooks/use-loader';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import Loader from "@/components/loader/loader.tsx";

/**
 * Loader示例组件
 * 展示如何使用useLoader hook控制全局加载状态
 */
const LoaderExample = () => {
  // 使用自定义Hook获取加载器控制方法
  const { showGlobalLoader, hideGlobalLoader } = useLoader();

  // 模拟异步操作
  const simulateAsyncOperation = () => {
    showGlobalLoader(); // 显示加载器
    
    // 3秒后自动隐藏加载器
    setTimeout(() => {
      hideGlobalLoader();
    }, 3000);
  };

  return (
    <div className="container py-10">
      <h1 className="text-2xl font-bold mb-6">全局加载器示例</h1>
      
      <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
        <Card>
          <CardHeader>
            <CardTitle>基本用法</CardTitle>
            <CardDescription>
              点击按钮显示全局加载器,3秒后自动隐藏
            </CardDescription>
          </CardHeader>
          <CardContent>
            <Button onClick={simulateAsyncOperation}>
              显示加载器 (3秒)
            </Button>
          </CardContent>
        </Card>

        <Card>
          <CardHeader>
            <CardTitle>手动控制</CardTitle>
            <CardDescription>
              分别使用按钮手动控制加载器的显示和隐藏
            </CardDescription>
          </CardHeader>
          <CardContent className="flex gap-4">
            <Loader/>
            <Button onClick={showGlobalLoader} variant="default">
              显示加载器
            </Button>
            <Button onClick={hideGlobalLoader} variant="outline">
              隐藏加载器
            </Button>
          </CardContent>
        </Card>

        <Card>
          <CardHeader>
            <CardTitle>实际应用场景</CardTitle>
            <CardDescription>
              在数据加载过程中显示加载状态
            </CardDescription>
          </CardHeader>
          <CardContent>
            <Button
              onClick={() => {
                showGlobalLoader();
                // 模拟API请求
                setTimeout(() => {
                  hideGlobalLoader();
                  // 显示成功消息
                  alert('数据加载成功!');
                }, 2000);
              }}
            >
              加载数据
            </Button>
          </CardContent>
        </Card>

        <Card>
          <CardHeader>
            <CardTitle>表单提交示例</CardTitle>
            <CardDescription>
              在表单提交过程中显示加载状态
            </CardDescription>
          </CardHeader>
          <CardContent>
            <Button
              onClick={() => {
                showGlobalLoader();
                // 模拟表单提交
                setTimeout(() => {
                  hideGlobalLoader();
                  // 显示成功消息
                  alert('表单提交成功!');
                }, 1500);
              }}
            >
              提交表单
            </Button>
          </CardContent>
        </Card>
      </div>

      <div className="mt-8 p-4 bg-gray-100 rounded-md">
        <h2 className="text-lg font-semibold mb-2">使用说明</h2>
        <p className="mb-2">1. 导入useLoader hook:<code>import {'{ useLoader }'} from '@/hooks/use-loader';</code></p>
        <p className="mb-2">2. 在组件中使用hook:<code>const {'{ showGlobalLoader, hideGlobalLoader }'} = useLoader();</code></p>
        <p className="mb-2">3. 调用方法显示/隐藏加载器:<code>showGlobalLoader()</code><code>hideGlobalLoader()</code></p>
        <p>4. 确保在应用根组件中已引入GlobalLoader组件</p>
      </div>
    </div>
  );
};

export default LoaderExample;