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

React JS中更新searchTerm状态后为何无法从API拉取到更新后的影视数据

核心问题根源
  • 原LandingPage中定义的onchange函数没有加const/let声明,实际会挂载为全局window属性,从未绑定任何触发逻辑,完全不参与组件流程,path/loadpath两个局部变量从未被有效赋值
  • 核心数据请求的useEffect依赖数组为空([]):组件首次挂载后就再也不会重新执行,完全不会响应searchTerm的状态变化
  • path/loadpath是用var声明的普通局部变量,非响应式状态:每次组件重渲染时都会被重置为undefined,无法持久存储计算好的请求地址
  • 数据拉取逻辑存在闭包陷阱:fetchMovies内的状态更新依赖组件首次渲染时的旧闭包值,即使触发重新请求也会拿到过期的状态数据
  • 滚动事件监听没有做卸载清理,长期运行会造成内存泄漏
具体修改方案
  1. 移除冗余无效代码
    • 删掉LandingPage中所有用var声明的path/loadpath变量,以及从未被调用的全局onchange函数,这些代码完全不参与实际业务流程,还会造成全局污染
  2. 重构数据请求副作用
    • 把原首次拉取数据的useEffect改为将searchTerm纳入依赖数组,只要searchTerm变化就自动触发重新请求
    • 每次触发新请求(包括搜索词变化、首次加载)时,先重置之前的电影列表、主图、分页状态,避免新旧搜索结果和热门电影数据混杂
    • 动态生成请求地址的逻辑收敛为公共方法,直接根据当前searchTerm值判断是拉取热门电影还是匹配搜索词的结果
  3. 修正闭包导致的状态更新异常
    • 所有状态更新改成函数式写法,直接拿到最新的状态值做更新,避免引用旧的闭包快照
  4. 修正加载更多的逻辑
    • 把loadMoreItems里的请求地址生成逻辑和初始请求保持一致,根据当前的searchTerm动态拼接对应分页的API地址,不依赖易丢失的临时局部变量
  5. 补全滚动监听的生命周期清理
    • 给滚动事件的useEffect加上组件卸载时移除事件监听的逻辑,避免内存泄漏
核心修改后的关键代码片段
import React, { useEffect, useState, useRef } from 'react'
import { Typography, Row } from 'antd';
import { API_URL, API_KEY, IMAGE_BASE_URL, IMAGE_SIZE, POSTER_SIZE } from '../../Config'
import MainImage from './Sections/MainImage'
import GridCard from '../../commons/GridCards'
import SearchMenu from '../LandingPage/Sections/SearchMenu'
const { Title } = Typography;

function LandingPage(props) {
    const [searchTerm, setSearchTerm] = useState('');
    const buttonRef = useRef(null);
    const [Movies, setMovies] = useState([])
    const [MainMovieImage, setMainMovieImage] = useState(null)
    const [Loading, setLoading] = useState(true)
    const [CurrentPage, setCurrentPage] = useState(0)

    // 公共方法:根据当前搜索词和页码生成请求地址
    const getEndpoint = (targetPage) => {
        if (searchTerm) {
            return `${API_URL}search/movie?api_key=${API_KEY}&language=en-US&query=${searchTerm}&page=${targetPage}`;
        }
        return `${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=${targetPage}`;
    }

    // 搜索词变化时自动重新拉取第一页数据
    useEffect(() => {
        // 状态重置,避免新旧数据混杂
        setMovies([])
        setMainMovieImage(null)
        setCurrentPage(0)
        setLoading(true)

        fetch(getEndpoint(1))
            .then(res => res.json())
            .then(result => {
                setMovies(result.results)
                setMainMovieImage(result.results[0])
                setCurrentPage(result.page)
                setLoading(false)
            })
            .catch(err => console.error('请求错误:', err))
    }, [searchTerm])

    // 修正滚动监听的副作用
    useEffect(() => {
        const handleScrollEvent = () => {
            const windowHeight = "innerHeight" in window ? window.innerHeight : document.documentElement.offsetHeight;
            const body = document.body;
            const html = document.documentElement;
            const docHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
            const windowBottom = windowHeight + window.pageYOffset;
            if (windowBottom >= docHeight - 1) {
                buttonRef.current.click();
            }
        }
        window.addEventListener("scroll", handleScrollEvent);
        // 组件卸载时清理监听
        return () => window.removeEventListener("scroll", handleScrollEvent)
    }, [])

    const fetchMovies = (endpoint) => {
        setLoading(true)
        fetch(endpoint)
            .then(result => result.json())
            .then(result => {
                // 函数式状态更新,避免闭包拿到旧值
                setMovies(prev => [...prev, ...result.results])
                setMainMovieImage(prev => prev || result.results[0])
                setCurrentPage(result.page)
                setLoading(false)
            })
            .catch(error => console.error('Error:', error))
    }

    const loadMoreItems = () => {
        const nextPage = CurrentPage + 1
        fetchMovies(getEndpoint(nextPage));
    }

    // 以下JSX部分和原实现保持一致
    return (
        <div>
            <div className="menu__container menu_search">
                <SearchMenu mode="horizontal" onChange={value => setSearchTerm(value)} />
            </div>
            {/* 剩余渲染逻辑省略,和原代码一致 */}
        </div>
    )
}

export default LandingPage

注:SearchMenu组件原有传值逻辑无需调整,只要父组件正确响应searchTerm的变化触发请求即可实现需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.27 00:15:03