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

解决React useEffect依赖警告与Feed组件滚动恢复的冲突

解决Feed组件滚动恢复的ESLint依赖警告问题

我开发了一个Feed组件,需求是在多个Feed标签页间切换时保留滚动位置:比如在"localhost/feed-one"滚动至500px,切换到"localhost/feed-two"后再返回,滚动位置应恢复至500px。当前代码功能符合预期,但ESLint抛出警告:"React Hook useEffect has a missing dependency: 'reScroll'. Either include it or remove the dependency array."

如果将reScroll加入useEffect的依赖数组,会在location.pathname变化但新帖子尚未加载时触发该useEffect,导致无法正确恢复滚动位置(例如帖子未加载时滚动到250px,加载后该值被覆盖,无法回到500px)。

Feed组件代码

import React, { useState, useEffect } from 'react';
import { useParams, useLocation } from 'react-router-dom';
import Loader from '../Loader/Loader';
import Post from '../Post/Post';
import Data, { Post as PostType } from '../../emulateAPI/Data';
import useScrollRestoration from '../../hooks/useScrollRestoration';
import './Feed.scss';

interface FeedProps {
  data: Data;
}

const Feed: React.FC<FeedProps> = (props) => {
  const { data } = props;
  const { categorySlug } = useParams<string>();
  const [isLoading, setIsLoading] = useState<boolean>();
  const [posts, setPosts] = useState<PostType[]>([]);
  const location = useLocation();
  const reScroll = useScrollRestoration();

  useEffect(() => {
    setIsLoading(true);
    const category = data.getCategoryBySlug(categorySlug);
    if (category) {
      setPosts(data.getPostsByCategory(category));
    }
    setIsLoading(false);
  }, [categorySlug, data]);

  useEffect(() => {
    reScroll();
  }, [posts]); // 此处触发ESLint警告:缺少reScroll依赖

  return (
    <>
      {isLoading ? (
        <Loader />
      ) : (
        <div className='feed'>
          {posts.map((post, index) => (
            <Post key={index} post={post} />
          ))}
        </div>
      )}
    </>
  );
}

export default Feed;

自定义Hook useScrollRestoration代码

import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

const useScrollRestoration = () => {
  const location = useLocation();

  useEffect(() => {
    const onScroll = () => {
      sessionStorage.setItem(location.pathname, window.scrollY.toString());
    };
    window.addEventListener('scroll', onScroll);
    return () => {
      window.removeEventListener('scroll', onScroll);
    };
  }, [location.pathname]);

  return (() => {
    const scrollY = sessionStorage.getItem(location.pathname);
    window.scrollTo(0, +(scrollY || 0));
  });
};

export default useScrollRestoration;

解决方案

方案1:用useCallback稳定reScroll引用(推荐)

问题根源是useScrollRestoration每次渲染都会返回新的函数引用,导致加入依赖数组后会频繁触发。用useCallback包裹返回函数,确保引用仅在location.pathname变化时更新,同时调整触发时机为加载完成后:

  1. 修改useScrollRestoration:
import { useEffect, useCallback } from 'react';
import { useLocation } from 'react-router-dom';

const useScrollRestoration = () => {
  const location = useLocation();

  useEffect(() => {
    const onScroll = () => {
      sessionStorage.setItem(location.pathname, window.scrollY.toString());
    };
    window.addEventListener('scroll', onScroll);
    return () => {
      window.removeEventListener('scroll', onScroll);
    };
  }, [location.pathname]);

  // 使用useCallback稳定函数引用
  const reScroll = useCallback(() => {
    const scrollY = sessionStorage.getItem(location.pathname);
    window.scrollTo(0, +(scrollY || 0));
  }, [location.pathname]);

  return reScroll;
};

export default useScrollRestoration;
  1. 调整Feed组件中的useEffect:
useEffect(() => {
  // 仅在帖子加载完成后恢复滚动
  if (!isLoading) {
    reScroll();
  }
}, [posts, isLoading, reScroll]);

这样既满足ESLint的依赖要求,又能保证只有当帖子加载完成后才恢复滚动位置,避免未加载时的错误触发。

方案2:临时忽略ESLint警告(不推荐)

如果不想修改Hook实现,可以在警告行添加ESLint注释,但需要明确说明原因:

useEffect(() => {
  reScroll();
}, [posts]); // eslint-disable-next-line react-hooks/exhaustive-deps -- reScroll引用不稳定,但仅需在posts变化时触发

这种方法简单,但会隐藏潜在的依赖问题,长期维护不推荐。

方案3:调整滚动恢复的触发时机

直接根据location和isLoading来触发滚动恢复,而不是监听posts变化:

useEffect(() => {
  if (!isLoading) {
    reScroll();
  }
}, [location, isLoading]);

这种方法更贴合业务逻辑:每次路由切换完成且帖子加载好后,恢复滚动位置,无需依赖posts数组变化。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 10:45:09