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

使用useRef钩子滚动动态数量标签页时的异常问题

MUI Base Tabs 状态同步与滚动定位异常问题

复现步骤 🕹

组件源代码

import { useContext, useEffect, useRef } from 'react';
import * as React from 'react';
import { styled } from '@mui/system';
import Tabs from '@mui/base/Tabs';
import TabsList from '@mui/base/TabsList';
import TabPanel from '@mui/base/TabPanel';
import { buttonClasses } from '@mui/base/Button';
import Tab, { tabClasses } from '@mui/base/Tab';
import { Box, Typography } from '@mui/material';
import getDatesInMonth from '@/utils/getDatesInMonth';
import { dateContext } from '@/pages/mob';
import { useTabs } from '@mui/base';

export default function DayScrollView({ dates }) {
  const [activeDate, setActiveDate] = useContext(dateContext)
  const tabsRef = useRef(null);

  useEffect(() => {
    const activeTab = tabsRef.current.querySelector(`.${tabClasses.root}[aria-selected="true"]`);
    console.log(activeTab)
    if (activeTab) {
      activeTab.scrollIntoView({
        behavior: 'smooth',
        block: 'center',
        inline: 'center',
      });
    }
  }, [activeDate, tabsRef])

  return <Box sx={
    {
      overflowX: 'scroll',
      scrollbarWidth: 'none',
      msOverflowStyle: 'none',
      '&::-webkit-scrollbar': {
        display: 'none',
      },
    }
  }>
    <Tabs
      // ref={tabsRef}
      value={activeDate.toDateString()}
      onChange={(event, newValue) => {
        console.log('Event type:', event.type);
        console.log('Target element:', event.target);
        console.log(`Tab ${newValue} was clicked.`);
        console.log(event)
        if (activeDate.toDateString() !== newValue) setActiveDate(new Date(newValue))
      }}>
      <StyledTabsList ref={tabsRef}>
        {dates.map((date, index) => {
          return (
            <StyledTab
              // disableRipple={true}
              key={date.toDateString()}
              value={date.toDateString()}

              onChange={() => console.log('CLICKED ' + date.toDateString())}>
              <Typography variant='subtitle1'>{date.toLocaleString('default', { weekday: 'short' })}</Typography>
              <Typography variant='h5'>{date.getDate()}</Typography>
            </StyledTab>
          )
        })}
      </StyledTabsList>
    </Tabs>
  </Box>
}

const StyledTab = styled(Tab)`
  color: #121212;
  cursor: pointer;
  ... 更多样式

`;


const StyledTabsList = styled(TabsList)(
  ({ theme }) => `
  padding-right: 1rem;
  ... 更多样式
  `,
);

使用该组件的父文件代码

import * as React from 'react';
import { AppbarDayScreen } from '@/components/mobile/home/Navbar';
import { useState } from 'react';
import { createContext } from 'react';
import DayScrollView from '@/components/mobile/home/DayScrollView';
import getDatesInMonth from '@/utils/getDatesInMonth';


const dateContext = createContext()

export default function DayScreen() {
    const [activeDate, setActiveDate] = useState(new Date());
    const [dates, setDates] = useState(getDatesInMonth(activeDate))

    React.useEffect(() => { setDates(getDatesInMonth(activeDate)) }, [activeDate])
    return (
        <dateContext.Provider value={[activeDate, setActiveDate]}>
            <AppbarDayScreen />
            <DayScrollView dates={dates} />
        </dateContext.Provider>
    );
}

export { dateContext }

当前行为 😯

  • 当activeDate变更时,Tabs组件的onChange回调函数不会触发
  • 首次加载页面时,useEffect里的activeTab能正确指向当前激活的标签页;但activeDate变更后,useEffect执行时,activeTab仍然指向之前的标签页,哪怕该标签页的aria-selected属性已经变为false

预期行为 🤔

tabsRef.current.querySelector(.${tabClasses.root}[aria-selected="true"]);应该能正确指向变更后的激活标签页,并且完成滚动定位。

上下文 🔦

我做了一个横向滚动的日期标签栏,点击导航栏的“月份”按钮可以选择日期,进而修改activeDate这个useState变量。activeDate会传递给标签组件,希望每次activeDate变更时,页面能自动滚动到对应的日期标签位置。


解决方案

问题核心是React状态更新后的DOM同步时机:当activeDate变更后,Tabs组件需要重新渲染来更新标签的aria-selected状态,但你的useEffect在activeDate变更后立即执行,此时DOM还未完成更新,所以查询到的还是旧的标签元素。

修复方案1:利用requestAnimationFrame等待DOM更新

把DOM查询逻辑放到requestAnimationFrame中,确保DOM完成更新后再执行滚动操作:

useEffect(() => {
  const updateScroll = () => {
    const activeTab = tabsRef.current.querySelector(`.${tabClasses.root}[aria-selected="true"]`);
    console.log(activeTab)
    if (activeTab) {
      activeTab.scrollIntoView({
        behavior: 'smooth',
        block: 'center',
        inline: 'center',
      });
    }
  };

  requestAnimationFrame(updateScroll);
}, [activeDate])

修复方案2:直接通过日期值定位标签(更可靠)

不需要依赖aria-selected属性,直接通过标签的value属性定位目标元素,避免DOM状态同步问题:

useEffect(() => {
  const targetTabValue = activeDate.toDateString();
  const activeTab = tabsRef.current.querySelector(`.${tabClasses.root}[value="${targetTabValue}"]`);
  if (activeTab) {
    activeTab.scrollIntoView({
      behavior: 'smooth',
      block: 'center',
      inline: 'center',
    });
  }
}, [activeDate])

额外说明

Tabs组件的onChange仅在用户手动点击标签时触发,通过修改value属性(比如从父组件传递activeDate)切换标签时,onChange不会执行,这是正常的设计逻辑。如果需要在activeDate变更时执行额外逻辑,应该放到监听activeDate的useEffect中,而非依赖Tabs的onChange。


内容的提问来源于stack exchange,提问作者Curious Learner

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 22:22:45