React实现切换板块后函数仅执行一次的方案求助
解决Pokemon页面切换板块时重复调用获取地点函数的问题
开发Pokemon网页应用时,详情页包含基础信息、招式查看、捕捉地点三个板块。点击「捕捉地点」按钮会触发获取地点的函数,但切换到其他板块再返回该板块时,这个函数会重复执行。尝试过memo、useCallback、useMemo均未解决问题,相关代码如下:
父组件代码
import { useCallback, useMemo, useState } from 'react'; import { useParams, Navigate } from 'react-router-dom'; import { usePokemon } from '../../../hooks/usePokemon'; import { useSelector } from 'react-redux'; import { PokemonAside } from './components/PokemonAside'; import { PokemonBasic } from './components/PokemonBasic'; import '../../styles/PokemonSelected.css'; import { PokemonMoves } from './components/PokemonMoves'; import { PokemonPlaces } from './components/PokemonPlaces'; export const PokemonSelected = () => { const { activePokemon, isLoadingPokemons } = useSelector(state => state.pokemon) const { startSearchingPokemon } = usePokemon(); const { name } = useParams(); const [ view, setView ] = useState({section: 1}); useMemo(() => { startSearchingPokemon(name); }, [name]); if (activePokemon == false) { return <Navigate to='/'/>; }; const clickPlaces = useCallback(() => { setView({section: 3}) }, [name]); return ( <div className='container'> { (isLoadingPokemons == true) ? <div>cargando...</div> : ( <div className='row'> <PokemonAside pokemon={ activePokemon }/> <div className="col-xl-8 col-lg-8"> <div className="pokemon-information"> <h3>Information</h3> <hr /> <div className="pokemon-sections-information"> <button type="button" className="btn btn-primary" onClick={() => setView({section: 1})}>Basic</button> <button type="button" className="btn btn-danger" onClick={() => setView({section: 2})}>Moves</button> <button type="button" className="btn btn-info" onClick={ clickPlaces }>Where to catch it?</button> </div> <hr /> { view.section === 1 ? <PokemonBasic abilities={activePokemon.abilities} stats={activePokemon.stats}/> : view.section === 2 ? <PokemonMoves moves={activePokemon.moves}/> : view.section === 3 ? <PokemonPlaces id={activePokemon.id}/> : '' } </div> </div> </div> ) } </div> ); };
子组件代码
import { useMemo, useCallback, useEffect, memo } from 'react'; import { usePokemon } from '../../../../hooks/usePokemon'; import { useSelector } from 'react-redux'; export const PokemonPlaces = memo(({id}) => { const { placesPokemon } = useSelector(state => state.pokemon) const { startSearchingPlaces } = usePokemon(); useEffect(() => { startSearchingPlaces(id); //this function should only execute once after executed for the first time }, []); return ( <div>Pokemon places</div> ) });
问题原因
当前用三元表达式切换板块时,PokemonPlaces组件会在切换到其他板块时被卸载,切回时重新挂载。组件挂载时,内部空依赖的useEffect会再次执行,导致重复调用startSearchingPlaces。memo只能阻止props不变时的重复渲染,无法解决卸载后重新挂载的问题。
解决方法
方法1:基于Redux缓存判断是否发起请求
在子组件的useEffect中,先检查Redux中是否已有当前Pokemon的地点数据,仅在无数据时发起请求:
export const PokemonPlaces = memo(({id}) => { const { placesPokemon } = useSelector(state => state.pokemon) const { startSearchingPlaces } = usePokemon(); useEffect(() => { // 检查是否已有对应id的地点数据,无数据才请求 if (!placesPokemon || placesPokemon.id !== id) { startSearchingPlaces(id); } }, [id, placesPokemon, startSearchingPlaces]); // 补充必要依赖,避免闭包问题 return ( <div>Pokemon places</div> ) });
方法2:保持组件挂载状态,控制显示隐藏
修改父组件的渲染逻辑,让三个板块组件始终保持挂载,通过CSS控制显示/隐藏,避免组件卸载:
// 父组件返回部分替换原有三元表达式 <div className="sections-container"> <PokemonBasic abilities={activePokemon.abilities} stats={activePokemon.stats} style={{ display: view.section === 1 ? 'block' : 'none' }} /> <PokemonMoves moves={activePokemon.moves} style={{ display: view.section === 2 ? 'block' : 'none' }} /> <PokemonPlaces id={activePokemon.id} style={{ display: view.section === 3 ? 'block' : 'none' }} /> </div>
这样组件只会挂载一次,useEffect中的请求也只会执行一次。
方法3:父组件统一控制请求时机
将获取地点的逻辑移到父组件,通过状态记录是否已请求过当前Pokemon的地点数据:
// 父组件新增状态 const [hasFetchedPlaces, setHasFetchedPlaces] = useState(false); // 切换Pokemon时重置状态 useEffect(() => { setHasFetchedPlaces(false); }, [name]); // 修改点击捕捉地点的回调 const clickPlaces = useCallback(() => { setView({section: 3}); // 未请求过才发起请求 if (!hasFetchedPlaces) { startSearchingPlaces(activePokemon.id); setHasFetchedPlaces(true); } }, [name, activePokemon.id, hasFetchedPlaces]);
此时子组件仅负责渲染数据,无需处理请求逻辑。
内容的提问来源于stack exchange,提问作者emi
相关产品推荐
相关产品推荐

