如何解决Material-UI Menu重渲染后滚动位置重置问题?
我刚遇到过类似的情况,来给你拆解下原因和解决办法哈~
为什么Menu会重置滚动位置,而原生<ul>不会?
原生<ul>在重渲染时,只是更新内部的子元素,DOM容器本身没有被卸载或重建,所以浏览器会自动保留它的滚动位置。但Material-UI的Menu组件内部结构更复杂:它是基于Popover实现的,列表内容被包裹在Paper和MenuList组件中。当你的状态更新(比如items变化)时,Menu内部可能因为组件的重新渲染逻辑,导致滚动容器(也就是MenuList对应的DOM元素)被重新创建,或者组件的key发生变化触发了卸载再挂载,这样滚动位置就会被重置。
解决办法:手动保存并恢复滚动位置
我们可以通过ref记录滚动容器的滚动位置,在组件更新后把位置恢复回去,具体步骤如下:
1. 类组件版本
首先要精准拿到Menu内部负责滚动的元素,也就是MenuList。你可以自定义Menu内部的MenuList并给它加ref:
import { Menu, MenuList } from '@material-ui/core'; class YourMenuComponent extends React.Component { // 用来保存滚动容器的ref menuListRef = React.createRef(); // 记录滚动位置 scrollPosition = 0; // 监听滚动事件,保存当前滚动位置 handleMenuScroll = (e) => { this.scrollPosition = e.target.scrollTop; }; // 组件更新后恢复滚动位置 componentDidUpdate(prevProps) { // 当items变化时(也就是触发重渲染的原因) if (prevProps.items !== this.props.items) { const scrollContainer = this.menuListRef.current; if (scrollContainer) { scrollContainer.scrollTop = this.scrollPosition; } } } render() { const { onChange, items } = this.props; return ( <Menu onChange={onChange}> <MenuList ref={this.menuListRef} onScroll={this.handleMenuScroll} > {items} </MenuList> </Menu> ); } }
2. 函数组件版本(Hook写法)
如果是函数组件,可以用useRef和useEffect来实现:
import { Menu, MenuList } from '@material-ui/core'; import { useRef, useEffect } from 'react'; const YourMenuComponent = ({ onChange, items }) => { const menuListRef = useRef(null); const scrollPosition = useRef(0); const handleMenuScroll = (e) => { scrollPosition.current = e.target.scrollTop; }; useEffect(() => { const scrollContainer = menuListRef.current; if (scrollContainer) { scrollContainer.scrollTop = scrollPosition.current; } }, [items]); // 当items变化时触发恢复 return ( <Menu onChange={onChange}> <MenuList ref={menuListRef} onScroll={handleMenuScroll} > {items} </MenuList> </Menu> ); };
额外提示:减少不必要的重渲染
如果你的items每次都是新创建的数组(比如用map生成时没有稳定的key),会导致Menu频繁重渲染。可以给每个菜单项加上唯一的key,或者用React.memo包裹菜单项组件,减少不必要的渲染,这样也能间接降低滚动位置丢失的概率。
内容的提问来源于stack exchange,提问作者JIE WANG
相关产品推荐
相关产品推荐

