如何用React和Material UI v5(JavaScript)创建连续提示条组件
解决Material UI v5可复用SnackbarAlert组件的多次重渲染问题
你的代码出现多次重渲染的核心问题有两个:
- React元素作为message触发重复更新:React元素是引用类型,每次调用
setSnackContent(<>Hello, world!</>)都会生成新的元素引用,导致AlertSnackbar中监听message的useEffect反复执行,不断往队列中添加内容,触发连锁状态更新。 - 多useEffect依赖形成循环触发:第二个
useEffect同时依赖pack、content、open,状态更新后会再次触发该钩子,形成不必要的重渲染循环。
修改后的可复用组件代码
import React, { useEffect, useState, useCallback } from 'react' import Snackbar from '@mui/material/Snackbar'; import MuiAlert from '@mui/material/Alert'; const Alert = React.forwardRef(function Alert(props, ref) { return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />; }); export default function AlertSnackbar({ message, severity = "success", autoHideDuration = 6000, ...otherProps }) { const [open, setOpen] = useState(false); const [currentMessage, setCurrentMessage] = useState(null); const [queue, setQueue] = useState([]); // 缓存关闭函数,避免重复创建 const handleClose = useCallback(() => { setOpen(false); }, []); // 过渡结束后自动显示下一条提示 const handleExited = useCallback(() => { if (queue.length > 0) { const next = queue[0]; setCurrentMessage(next); setQueue(prev => prev.slice(1)); setOpen(true); } else { setCurrentMessage(null); } }, [queue]); // 处理新消息的加入与显示 useEffect(() => { if (!message) return; // 可选:避免重复添加相同内容(根据业务需求调整) const isDuplicate = currentMessage?.content === message || queue.some(item => item.content === message); if (isDuplicate) return; const newItem = { content: message, key: Date.now() }; setQueue(prev => [...prev, newItem]); // 当前无提示时直接显示 if (!currentMessage) { setCurrentMessage(newItem); setOpen(true); } }, [message, currentMessage]); return ( <Snackbar open={open} autoHideDuration={autoHideDuration} onClose={handleClose} {...otherProps} TransitionProps={{ onExited: handleExited }} key={currentMessage?.key} > <Alert onClose={handleClose} severity={severity} sx={{ width: '100%' }}> {currentMessage?.content} </Alert> </Snackbar> ); }
修改后的使用示例
import React, { useState } from 'react' import { Button } from '@mui/material' import AlertSnackbar from '../components/AlertSnackbar' export default function SomeComponent() { const [snackContent, setSnackContent] = useState(null); const handleTestClick = () => { // 优先传递字符串,避免引用类型重复触发更新 setSnackContent("Hello, world!"); // 如需复杂JSX,可直接传递,但建议添加唯一标识配合去重逻辑 // setSnackContent(<><b>Hello</b>, world!</>); }; return ( <> <Button onClick={handleTestClick}>Test</Button> <AlertSnackbar message={snackContent} anchorOrigin={{ horizontal: "center", vertical: "bottom" }} severity="success" /> </> ) }
关键优化点说明
- 避免React元素作为状态传递:改用字符串或普通数据传递提示内容,减少引用变化导致的重复更新;若需复杂JSX,可配合自定义ID实现精准去重。
- 缓存回调函数:用
useCallback缓存handleClose和handleExited,避免因函数引用变化触发组件重渲染。 - 合并逻辑减少钩子联动:将消息入队和显示逻辑整合到一个
useEffect中,避免多钩子之间的循环依赖触发不必要的重渲染。 - 可选去重逻辑:添加重复内容判断,防止重复操作导致队列堆积。
内容的提问来源于stack exchange,提问作者KidMe
相关产品推荐
相关产品推荐

