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

如何用React和Material UI v5(JavaScript)创建连续提示条组件

解决Material UI v5可复用SnackbarAlert组件的多次重渲染问题

你的代码出现多次重渲染的核心问题有两个:

  1. React元素作为message触发重复更新:React元素是引用类型,每次调用setSnackContent(<>Hello, world!</>)都会生成新的元素引用,导致AlertSnackbar中监听message的useEffect反复执行,不断往队列中添加内容,触发连锁状态更新。
  2. 多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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 10:17:52