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

如何使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 04:55:23