React Native useEffect监听selectedSet变更时调用函数陷入死循环
问题根源
你当前的代码存在两个核心问题:
- 状态更新的异步性:
setSelectedSet是异步更新状态的操作,调用后立刻执行navigation.navigate时,拿到的pokemonCards一定是上一次渲染的旧值,不是最新请求返回的卡片数据。 - 无限循环的诱因:你的
useEffect没有对selectedSet做空值判断,初始渲染时selectedSet是空字符串,会直接触发接口请求。如果接口返回的空结果每次引用地址不同(哪怕内容都是空数组),setPokemonCards会触发组件重渲染,如果你其他逻辑里存在依赖pokemonCards修改selectedSet的代码,就会形成「selectedSet更新→发起请求→更新pokemonCards→selectedSet被修改」的无限循环。还有一种常见场景是你循环渲染TouchableHighlight时,没有用useCallback包裹onPress的处理函数,每次组件重渲染都会生成新的函数引用,导致列表项不必要的重渲染,放大了状态更新的副作用。
解决方案
最优方案(从根源避免循环)
完全可以去掉多余的useEffect和冗余的状态,直接在点击事件里处理请求,拿到结果后再跳转:
// 点击处理函数用useCallback包裹避免不必要的重渲染 const handleSetClick = useCallback(async (pokemonSet) => { // 直接发起卡片请求 const cards = await PokemonTCG.findCardsByQueries({ q: 'set.id:' + pokemonSet.id, pageSize: 10 }) // 请求成功后再跳转,直接传递拿到的卡片数据 navigation.navigate("SetDetails", { setID: pokemonSet.id, pokemonSetDetails: pokemonSet, pokemonCards: cards }); }, [navigation]) // 渲染TouchableHighlight <TouchableHighlight key={pokemonSet.id} onPress={() => handleSetClick(pokemonSet)} >
兼容原有逻辑的修改方案
如果你一定要保留原来的useEffect写法,需要做两处修改:
- 给
useEffect加空值判断,selectedSet为空时不发起请求 - 跳转逻辑移到
useEffect里,等pokemonCards更新完成后再跳转,不要在点击时立刻跳转
const [pokemonCards, setPokemonCards] = useState([]); const [selectedSet, setSelectedSet] = useState(""); // 新增状态存储选中的集合完整信息 const [selectedSetInfo, setSelectedSetInfo] = useState(null); useEffect(() => { // 空值直接返回,不发起请求 if (!selectedSet || !selectedSetInfo) return; const getPokemonCards = async () => { const cards = await PokemonTCG.findCardsByQueries({ q: 'set.id:' + selectedSet, pageSize: 10 }) setPokemonCards(cards); // 拿到卡片数据后再执行跳转 navigation.navigate("SetDetails", { setID: selectedSet, pokemonSetDetails: selectedSetInfo, pokemonCards: cards }); } getPokemonCards(); }, [selectedSet, selectedSetInfo, navigation]); // 点击时只存储选中的集合信息,不做跳转 <TouchableHighlight key={pokemonSet.id} onPress = {() => { setSelectedSet(pokemonSet.id); setSelectedSetInfo(pokemonSet); }} >
内容的提问来源于stack exchange,提问作者Papi
相关产品推荐
相关产品推荐

