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

React+TypeScript全局Snackbar组件实现与调用求助

全局Material-UI Snackbar实现方案

原代码问题分析

  • 导入错误:Slide和Alert未正确从子路径导入,导致组件无法正常渲染
  • HOC参数冗余:无需将Alert作为参数传入,直接使用导入的组件即可
  • 关闭逻辑缺失:handleClose函数未更新open状态,导致Snackbar无法自动或手动关闭
  • 硬编码severity:Alert的severity固定为"error",未关联状态中的severity值
  • 类型缺失:未给包裹组件的props添加snackbarShowMessage的类型声明,TypeScript会报错

修正后的SnackbarHOC代码

import { IconButton, Snackbar } from '@mui/material';
import Slide from '@mui/material/Slide';
import Alert from '@mui/material/Alert';
import { useState } from 'react';
import CloseIcon from '@mui/icons-material/Close';

// 定义Snackbar调用方法的类型
type SnackbarMethods = {
  snackbarShowMessage: (
    message: string,
    severity?: 'success' | 'error' | 'warning' | 'info',
    duration?: number
  ) => void;
};

// 合并原有组件props与Snackbar方法类型
type WrappedProps<T> = T & SnackbarMethods;

function SnackbarHOC<T>(WrappedComponent: React.ComponentType<WrappedProps<T>>) {
  return (props: T) => {
    const [open, setOpen] = useState(false);
    const [message, setMessage] = useState("I'm a custom snackbar");
    const [duration, setDuration] = useState(2000);
    const [severity, setSeverity] = useState<'success' | 'error' | 'warning' | 'info'>("success");

    const showMessage = (message: string, severity = "success", duration = 2000) => {
      setMessage(message);
      setSeverity(severity);
      setDuration(duration);
      setOpen(true);
    };

    const handleClose = (event: React.SyntheticEvent | Event, reason?: string) => {
      if (reason === 'clickaway') return;
      setOpen(false); // 添加关闭状态更新
    };

    return (
      <>
        <WrappedComponent {...props} snackbarShowMessage={showMessage} />
        <Snackbar
          anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
          autoHideDuration={duration}
          open={open}
          onClose={handleClose}
          TransitionComponent={Slide}
        >
          <Alert 
            severity={severity} // 使用状态中的severity值
            sx={{ width: '100%' }}
            action={
              <IconButton
                aria-label="close"
                color="inherit"
                size="small"
                onClick={handleClose}
              >
                <CloseIcon fontSize="inherit" />
              </IconButton>
            }
          >
            {message}
          </Alert>
        </Snackbar>
      </>
    );
  };
};

export default SnackbarHOC;

组件调用方式

1. 单个组件包裹使用

比如你有一个UserProfile组件需要调用Snackbar:

import SnackbarHOC from '../../SnackbarHOC';

// 定义组件自身props类型
type UserProfileProps = {
  username: string;
};

function UserProfile({ username, snackbarShowMessage }: UserProfileProps) {
  const handleUpdate = () => {
    // 调用Snackbar显示成功提示
    snackbarShowMessage('资料更新成功!', 'success', 3000);
    // 显示错误提示示例
    // snackbarShowMessage('更新失败,请重试', 'error');
  };

  return (
    <div>
      <h2>欢迎,{username}</h2>
      <button onClick={handleUpdate}>更新个人资料</button>
    </div>
  );
}

// 使用HOC包裹组件,注入Snackbar方法
export default SnackbarHOC(UserProfile);

2. 全局范围生效

如果希望整个应用都能使用Snackbar,直接在根组件外层包裹:

import SnackbarHOC from './SnackbarHOC';
import App from './App';

// 包裹根组件,全局注入Snackbar能力
export default SnackbarHOC(App);

更优全局方案:React Context实现

HOC需要逐个包裹组件,用Context可以实现无需传递props的全局调用:

1. 创建Snackbar Context

// SnackbarContext.tsx
import { createContext, useContext, useState, ReactNode } from 'react';
import { IconButton, Snackbar } from '@mui/material';
import Alert from '@mui/material/Alert';
import Slide from '@mui/material/Slide';
import CloseIcon from '@mui/icons-material/Close';

type SnackbarContextType = {
  showMessage: (
    message: string,
    severity?: 'success' | 'error' | 'warning' | 'info',
    duration?: number
  ) => void;
};

const SnackbarContext = createContext<SnackbarContextType | undefined>(undefined);

export function SnackbarProvider({ children }: { children: ReactNode }) {
  const [open, setOpen] = useState(false);
  const [message, setMessage] = useState('');
  const [duration, setDuration] = useState(2000);
  const [severity, setSeverity] = useState<'success' | 'error' | 'warning' | 'info'>('success');

  const showMessage = (message: string, severity = 'success', duration = 2000) => {
    setMessage(message);
    setSeverity(severity);
    setDuration(duration);
    setOpen(true);
  };

  const handleClose = (event: React.SyntheticEvent | Event, reason?: string) => {
    if (reason === 'clickaway') return;
    setOpen(false);
  };

  return (
    <SnackbarContext.Provider value={{ showMessage }}>
      {children}
      <Snackbar
        anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
        autoHideDuration={duration}
        open={open}
        onClose={handleClose}
        TransitionComponent={Slide}
      >
        <Alert
          severity={severity}
          sx={{ width: '100%' }}
          action={
            <IconButton
              aria-label="close"
              color="inherit"
              size="small"
              onClick={handleClose}
            >
              <CloseIcon fontSize="inherit" />
            </IconButton>
          }
        >
          {message}
        </Alert>
      </Snackbar>
    </SnackbarContext.Provider>
  );
}

export function useSnackbar() {
  const context = useContext(SnackbarContext);
  if (!context) {
    throw new Error('useSnackbar必须在SnackbarProvider内部使用');
  }
  return context;
}

2. 入口文件注入Provider

// index.tsx
import { SnackbarProvider } from './SnackbarContext';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <SnackbarProvider>
    <App />
  </SnackbarProvider>,
);

3. 任意组件中直接调用

// 任意业务组件
import { useSnackbar } from './SnackbarContext';

function OrderPage() {
  const { showMessage } = useSnackbar();

  const handleSubmitOrder = () => {
    showMessage('订单提交成功!', 'success');
  };

  return <button onClick={handleSubmitOrder}>提交订单</button>;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 07:20:27