如何使用useEffect和useCallback解决评论加载的无限循环问题?
解决React评论分页加载的无限循环与ESLint依赖警告问题
问题背景
要实现的功能:页面初始化时自动加载第一批评论,点击“加载更多”按钮加载后续分页内容。现有代码存在两个矛盾点:
- 因
fetchNextCommentsPage依赖comments状态,每次评论更新都会重新创建该回调,触发useEffect重复执行,导致无限循环 - 若移除
useEffect依赖列表中的fetchNextCommentsPage,功能正常但会触发ESLint的缺少依赖警告
原代码:
const [comments, setComments] = useState<CommentModel[]>([]); const [commentsLoading, setCommentsLoading] = useState(true); const [commentsLoadingError, setCommentsLoadingError] = useState(false); const [paginationEnd, setPaginationEnd] = useState(false); const fetchNextCommentsPage = useCallback(async function () { try { setCommentsLoading(true); setCommentsLoadingError(false); const continueAfterId = comments[comments.length - 1]?._id; const response = await BlogApi.getCommentsForBlogPost(blogPostId, continueAfterId); setComments([...comments, ...response.comments]); setPaginationEnd(response.paginationEnd); } catch (error) { console.error(error); setCommentsLoadingError(true); } finally { setCommentsLoading(false); } }, [blogPostId, comments]) useEffect(() => { fetchNextCommentsPage(); }, [fetchNextCommentsPage]);
解决方案
核心是避免在useCallback中直接依赖comments状态,改用setComments的函数式更新方式,既获取最新评论状态,又让useCallback的依赖列表保持稳定,打破循环。
修改后的代码:
const [comments, setComments] = useState<CommentModel[]>([]); const [commentsLoading, setCommentsLoading] = useState(true); const [commentsLoadingError, setCommentsLoadingError] = useState(false); const [paginationEnd, setPaginationEnd] = useState(false); const fetchNextCommentsPage = useCallback(async function () { try { setCommentsLoading(true); setCommentsLoadingError(false); // 直接用当前comments获取最后一条id,仅读取状态不会触发更新,无需担心循环 const continueAfterId = comments.length > 0 ? comments[comments.length - 1]._id : undefined; const response = await BlogApi.getCommentsForBlogPost(blogPostId, continueAfterId); // 函数式更新:通过prev参数获取最新的评论状态,无需依赖comments setComments(prevComments => [...prevComments, ...response.comments]); setPaginationEnd(response.paginationEnd); } catch (error) { console.error(error); setCommentsLoadingError(true); } finally { setCommentsLoading(false); } }, [blogPostId]) // 移除comments依赖,仅保留blogPostId useEffect(() => { fetchNextCommentsPage(); }, [fetchNextCommentsPage]);
进一步优化(可选)
如果连读取comments都不想放在useCallback里,可以单独维护lastCommentId状态,同步更新:
const [lastCommentId, setLastCommentId] = useState<string | undefined>(undefined); const fetchNextCommentsPage = useCallback(async function () { try { setCommentsLoading(true); setCommentsLoadingError(false); // 直接使用维护的lastCommentId,无需依赖comments const response = await BlogApi.getCommentsForBlogPost(blogPostId, lastCommentId); setComments(prevComments => { const newComments = [...prevComments, ...response.comments]; // 同步更新lastCommentId setLastCommentId(newComments.length > 0 ? newComments.at(-1)?._id : undefined); return newComments; }); setPaginationEnd(response.paginationEnd); } catch (error) { console.error(error); setCommentsLoadingError(true); } finally { setCommentsLoading(false); } }, [blogPostId, lastCommentId])
这样useCallback的依赖仅为blogPostId和lastCommentId,状态变化更可控,彻底避免循环风险。
内容的提问来源于stack exchange,提问作者Florian Walther
相关产品推荐
相关产品推荐

