解决React useEffect依赖警告与Feed组件滚动恢复的冲突
我开发了一个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变化时更新,同时调整触发时机为加载完成后:
- 修改
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;
- 调整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

