React Native不同页面同名State引发渲染异常的原因咨询
同名State引发React Native组件渲染异常的原因解析
问题场景
两个独立页面branchScreen.js和outcomeScreen.js曾使用同名stateoutcomes:前者存储汇总数据,后者存储完整数据集。先访问branchScreen再进入outcomeScreen后,后者的outcomes state更新时,页面停止重新渲染。将branchScreen的state名称改为outcomesToday后,问题解决。
核心原因
React组件的state是组件级完全隔离的,不同组件的同名state不会直接互相干扰。问题根源在于两个页面共用了OutcomeList子组件,结合React Navigation的页面复用机制和React的渲染优化逻辑:
- 组件复用:从
branchScreen导航到outcomeScreen时,React Navigation可能复用已加载的OutcomeList组件实例,而非销毁重建。 - 浅比较优化:如果
OutcomeList使用了React.memo(React默认性能优化手段,仅当props发生浅层次变化时才重新渲染),当outcomeScreen传入的outcomes与branchScreen之前传入的outcomes在浅比较中被判定为相同(比如初始状态均为null,或API返回的新数据与旧数据的引用/浅值巧合一致),OutcomeList就不会触发重新渲染,表现为outcomeScreen的state更新后页面无变化。
修改branchScreen的state名为outcomesToday后,传给OutcomeList的outcomes props变为全新的数据引用/值,React的浅比较能正确识别变化,从而触发OutcomeList重新渲染,问题得以解决。
相关代码
修改后的branchScreen.js
import { View, Text, SafeAreaView, ScrollView, Pressable, RefreshControl, } from "react-native"; import React, { useState, useEffect } from "react"; import { getData } from "../api/api-connections"; import IncomeList from "../components/IncomeList"; import OutcomeList from "../components/OutcomeList"; import { API_HOST } from "../utils/constants"; import { globalStyles } from "../utils/globalStyles"; import useAuth from "../hooks/useAuth"; import AsyncStorage from "@react-native-async-storage/async-storage"; import { useFocusEffect } from "@react-navigation/native"; export default function BranchScreen(props) { const { auth, branch, logout } = useAuth(); [incomesToday, setIncomesToday] = useState([]); [outcomesToday, setOutcomesToday] = useState(null); [orders, setOrders] = useState([]); [status, setStatus] = useState(false); [refreshing, setRefreshing] = useState(false); useFocusEffect( React.useCallback(() => { (async () => { await loadItems().catch((error) => console.log(error)); })(); }, [branch]) ); const onRefresh = React.useCallback(async () => { await loadItems().catch((error) => console.log(error)); setRefreshing(false); }, []); const loadItems = async () => { if (branch) { try { const response = await getData("/branches/" + branch, auth); setOutcomesToday(response.outcomes); setIncomesToday(response.incomes); setOrders(response.orders); setStatus(response.status); } catch (error) { console.log(error); } } }; const { navigation } = props; return ( <ScrollView style={[globalStyles.content, { marginTop: 40 }]} refreshControl={ <RefreshControl refreshing={refreshing} onRefresh={onRefresh} /> } > {branch ? ( <> <Text style={globalStyles.title_1}>Ventas hoy</Text> <IncomeList incomes={incomes} /> <Text style={globalStyles.title_1}>Gastos hoy</Text> <OutcomeList outcomes={outcomesToday} /> <Pressable style={globalStyles.button} onPress={() => { console.log("ir a todos los datos"); }} > <Text style={globalStyles.buttonText}>Ver todos los gastos</Text> </Pressable> </> ) : ( <Text style={globalStyles.title_1}>No hay datos que mostrar</Text> )} <Pressable style={globalStyles.button} onPress={logout}> <Text style={globalStyles.buttonText}>Salir</Text> </Pressable> </ScrollView> ); }
outcomeScreen.js
import { View, Text, ScrollView, Pressable, RefreshControl, ActivityIndicator, } from "react-native"; import React, { useEffect, useState } from "react"; import OutcomeList from "../components/OutcomeList"; import { useFocusEffect } from "@react-navigation/native"; import { getData } from "../api/api-connections"; import useAuth from "../hooks/useAuth"; import { globalStyles } from "../utils/globalStyles"; import { useNavigation } from "@react-navigation/native"; import { colors } from "../utils/constants"; export default function OutcomeScreen() { const { auth, branch } = useAuth(); [outcomes, setOutcomes] = useState(null); const [refreshing, setRefreshing] = React.useState(false); const navigation = useNavigation(); const goTo = () => { navigation.navigate("createOutcome"); }; const loadItems = async () => { try { const response = await getData("/outcomes", auth); setOutcomes(response.outcomes); } catch (error) { console.log(error); } }; useFocusEffect( React.useCallback(() => { (async () => { await loadItems().catch((error) => console.log(error)); })(); }, []) ); const onRefresh = React.useCallback(async () => { await loadItems().catch((error) => console.log(error)); setRefreshing(false); }, []); return ( <ScrollView style={[globalStyles.content, { marginTop: 40 }]} refreshControl={ <RefreshControl refreshing={refreshing} onRefresh={onRefresh} /> } > <Pressable style={globalStyles.button} onPress={goTo}> <Text style={globalStyles.buttonText}>Crear Gasto</Text> </Pressable> {outcomes ? ( <OutcomeList outcomes={outcomes} /> ) : ( <ActivityIndicator size="large" style={styles.spinner} color="black" /> )} </ScrollView> ); }
内容的提问来源于stack exchange,提问作者Cheko García
相关产品推荐
相关产品推荐

