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

迁移至Function/Hooks/Redux架构后react-select滚动同步失效问题

React类组件重构为函数组件后react-select滚动异常问题

问题背景

我正在重构应用,将顶层类组件改写为函数组件,同时使用useEffect Hooks和Redux,过程中遇到了滚动行为异常的问题。

  • 旧版本(运行正常):https://stopthegop.netlify.app/
  • 新版本(存在问题):https://61a2dbcce007ea00072b079c--stopthegop.netlify.app/

问题表现

我期望右侧div滚动时,左侧react-select组件的菜单列表同步滚动,保证右侧div顶部展示的主题始终在左侧菜单可见范围内。但实际情况为,滚动右侧div时,首次滚轮操作就会让左侧菜单自动回到顶部,后续还会触发3次重渲染,我手动触发滚动的代码完全不生效。我观察到正常运行的旧版本中左侧组件也会触发多次重渲染,但从未出现自动回到顶部的情况,我无法定位两个版本的差异原因,希望能得到相关解答,理解重渲染逻辑及修复方案。

初步排查

我初步排查后认为问题不来自顶层组件引用SearchBox组件的参数差异,两个版本的引用代码如下:

旧版本调用代码

<SearchBox
  groupedTopics={groupedTopics}
  topics={topics}
  ui={ui}
  vars={vars}
  inputValue={inputValue}
  stickyGroupHeadings={stickyGroupHeadings}
  searchRef={this.searchRef}
  filteringFunction={filteringFunction}
  isMenuOpen={isSearchMenuOpen(vars.IS_WIDE_SCREEN, selectionMade, isInitialHashValid, hashValue, hasFocus, anyTopicsForHash)}
  onFocus={this.handleSearchBoxFocus}
  onBlur={this.handleSearchBoxBlur}
  onClickGroupHeading={this.handleSearchBoxClickGroupHeading}
  onInputChange={this.handleSearchBoxInputChange}
  onSearchChange={this.handleSearchBoxSearchChange}
  onMenuScroll={this.handleSearchBoxMenuScroll}
/>

新版本调用代码

<SearchBox
  groupedTopics={groupedFilteredTopics}
  ui={ui}
  vars={vars}
  inputValue={inputValue}
  menuListRef={menuListRef}
  isMenuOpen={isSearchMenuOpen()}
  searchFilter={searchFilter}
  onClickGroupHeading={handleSearchBoxClickGroupHeading}
  onFocus={handleSearchBoxFocus}
  onBlur={handleSearchBoxBlur}
  onInputChange={handleSearchBoxInputChange}
  onScroll={handleSearchBoxScroll}
  onSelectTopic={handleSearchBoxSelectTopic}
/>

后续测试结论

测试发现旧版本中SearchBox是函数组件、Main是类组件时运行正常;将Main改为函数组件后问题出现,再把SearchBox改为类组件后问题又恢复正常。我原本是想通过改类组件使用shouldComponentUpdate方法做优化,但实际上不需要配置该方法仅改类组件就修复了问题。
目前测试结论:顶层组件和子组件仅有一个为函数组件时功能正常,二者同时为函数组件时就会出现滚动异常。我不认为当前是最终解决方案,函数组件本不该出现这类问题,请问这是什么原因导致的?是否是React本身的Bug?我希望能全量使用函数组件实现功能。


原因分析&修复方案

这不是React本身的Bug,是函数组件默认重渲染规则和react-select的菜单交互逻辑共同导致的问题:

  1. 类组件默认会对新老props做浅比较,props无变化就不会触发重渲染;但函数组件没有这个机制,只要父组件重渲染,子组件就会跟着重渲染,除非手动做缓存处理。
  2. react-select的菜单组件在重渲染时如果没有额外的滚动位置持久化逻辑,会默认把滚动条重置到顶部,你手动设置滚动的代码不生效,是因为重渲染发生在你滚动逻辑执行之后,新渲染的菜单DOM直接覆盖了你之前设置的滚动位置。
  3. 你可以任选以下方案实现全函数组件的需求:
  • 用React.memo包裹SearchBox函数组件,对传入的props做浅比较,避免不必要的重渲染。注意父组件传递的所有回调函数需要用useCallback包裹,引用类型的参数比如groupedFilteredTopics需要用useMemo包裹,保证重渲染时引用不变,否则React.memo不会生效。
  • 如果确实需要SearchBox重渲染,你可以把菜单的滚动位置存在useRef里,用useLayoutEffect在DOM更新完成后立刻把滚动位置赋值给菜单DOM,避免重渲染后位置被重置。
  • 先检查isSearchMenuOpen()的返回值是否在滚动时发生了不必要的切换,如果这个值频繁变动会导致react-select销毁重建菜单组件,滚动位置自然会被重置。

内容的提问来源于stack exchange,提问作者Dan Cancro

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 18:36:03