React中console.count多次执行的原因排查求助
组件频繁刷新(console.count()触发12次)的原因及优化
问题描述
在组件的useEffect下方使用console.count(),发现它被调用了12次,想明确频繁刷新的原因,以及是否和定义的多个state有关。
组件代码如下:
const { user } = UserAuth(); const { id } = useParams() const [count, setCount] = useState(() => { return id ? parseInt(id) : 5 }) const [name, setName] = useState() const [img, setImg] = useState() const [stats, setStats] = useState() const [types, setTypes] = useState() const [otherStats, setOtherStats] = useState() const [desc, setDesc] = useState() const [favoritePokemon, setFavoritePokemon] = useState() const navigate = useNavigate() const location = useLocation() useEffect(() => { navigate(`/PokemonPage/${count}`) fetch('https://pokeapi.co/api/v2/pokemon/' + count) .then(res => { if (res.status >= 400) { return setCount(1) } return res.json() }) .then(data => { setName(data.name) setImg(data.sprites.front_default) setTypes(data.types) setStats(data.stats) fetch(data.species.url) .then(res => res.json()) .then(speciesData => { setDesc(speciesData.flavor_text_entries[4]) setOtherStats({ "height": data.height, "weight": data.weight, "base_happiness": speciesData.base_happiness, "capture_rate": speciesData.capture_rate }) }) }) }, [count]) console.count()
原因分析
组件频繁刷新和多个state定义、当前代码逻辑都有关系,具体拆解:
- 多state更新触发多次重渲染:每次调用
setName、setImg、setTypes、setStats、setDesc、setOtherStats这些setState方法,都会单独触发组件重渲染。一次完整请求下来,你先后调用了6次setState,这就会带来6次重渲染(React在某些场景下不会自动合并更新)。 - useEffect的循环触发:
useEffect依赖count,当count变化时会执行逻辑;而navigate(/PokemonPage/${count})会修改路由,路由参数id变化又会触发useParams()更新,进而触发组件重渲染。如果请求返回400+状态码,setCount(1)会再次触发useEffect执行,形成一轮新的更新循环。 - 其他潜在触发源:
UserAuth()返回的user如果在渲染过程中发生变化,也会触发组件重渲染;组件首次挂载本身也会触发一次初始渲染。
优化方案
- 合并state更新:将分散的state合并为一个对象,减少
setState调用次数,从而减少重渲染:
const [pokemonData, setPokemonData] = useState({ name: '', img: '', types: [], stats: [], desc: '', otherStats: {} }); // 请求成功后一次性更新所有数据 setPokemonData({ name: data.name, img: data.sprites.front_default, types: data.types, stats: data.stats, desc: speciesData.flavor_text_entries[4], otherStats: { height: data.height, weight: data.weight, base_happiness: speciesData.base_happiness, capture_rate: speciesData.capture_rate } });
- 避免不必要的路由跳转:添加判断,仅当当前路由不匹配时才执行
navigate,防止重复触发路由更新:
if (location.pathname !== `/PokemonPage/${count}`) { navigate(`/PokemonPage/${count}`); }
- 添加请求清理逻辑:在
useEffect中使用AbortController,防止组件卸载后请求仍在执行,避免无效的setState触发重渲染:
useEffect(() => { const abortController = new AbortController(); const fetchPokemon = async () => { try { const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${count}`, { signal: abortController.signal }); if (res.status >= 400) { setCount(1); return; } const data = await res.json(); const speciesRes = await fetch(data.species.url, { signal: abortController.signal }); const speciesData = await speciesRes.json(); setPokemonData({ name: data.name, img: data.sprites.front_default, types: data.types, stats: data.stats, desc: speciesData.flavor_text_entries[4], otherStats: { height: data.height, weight: data.weight, base_happiness: speciesData.base_happiness, capture_rate: speciesData.capture_rate } }); if (location.pathname !== `/PokemonPage/${count}`) { navigate(`/PokemonPage/${count}`); } } catch (err) { if (err.name !== 'AbortError') { console.error('请求失败:', err); } } }; fetchPokemon(); return () => abortController.abort(); // 组件卸载时终止请求 }, [count, location.pathname, navigate]);
- 检查
UserAuth的稳定性:确认UserAuth()返回的user对象不会每次渲染都生成新引用,必要时用useMemo包裹或调整Auth库的使用方式,避免因user变化触发无意义的重渲染。
内容的提问来源于stack exchange,提问作者Martin Thomas
相关产品推荐
相关产品推荐

