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
相关产品推荐
相关产品推荐

