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

