React Native应用调用API后组件首次不更新,需在VS Code保存两次才正常显示
问题根源
- Context层拿到API返回的用户数据后,仅写入AsyncStorage,未调用
setUser更新全局状态,不会触发任何依赖该状态的组件重渲染。 - WelcomeChamado页面的初始化useEffect只在组件挂载时执行一次,此时API请求大概率还未完成,AsyncStorage里的用户数据还是旧值/空值,后续API请求完成写入新值后,页面没有监听Storage变化,不会主动重新读取数据。
- 你在VS Code保存文件触发热更新时,组件重新挂载,此时AsyncStorage已经有了上次API写入的最新数据,所以才会正常显示。
修复方案
1. 修改user.js(Context文件)
拿到API返回数据后先更新全局状态,再写入持久化存储,同时补全Context未定义的状态变量:
import React, { createContext, useEffect, useState, useContext } from "react" import AsyncStorage from "@react-native-async-storage/async-storage" import ApiRequest from '../services/Api'; const UserContext = createContext({}) export const UserProvider = ({ children }) => { const [user, setUser] = useState({}) const [services, setServices] = useState([]) // 补全你Context value里用到的未声明变量,根据业务逻辑调整初始值 const [currentService, setCurrentService] = useState(null) const [serviceDate, setServiceDate] = useState('') const [serviceType, setServiceType] = useState('') useEffect(() => { let isSubscribed = true; const getStorage = async (isSubscribed) => { const storagedToken = await AsyncStorage.getItem('token'); const storagedId = await AsyncStorage.getItem('id'); if(storagedToken && storagedId) { isSubscribed ? getUser(storagedToken, storagedId) : null; } } getStorage(isSubscribed); return () => (isSubscribed = false); }, []) async function getUser(storagedToken, storagedId) { try { const resp = await ApiRequest.getUser(storagedToken, storagedId) const userData = resp.Cadastro // 新增:更新全局状态,触发组件重渲染 setUser(userData) await AsyncStorage.setItem('user', JSON.stringify(userData)); } catch (error) { console.log('Não foi possível recuperar os dados do usuário, tente novamente.') } } return ( <UserContext.Provider value={{ user, currentService, services, setServiceDate, setServiceType, setCurrentService }}> {children} </UserContext.Provider> ) } export const useUser = () => { return useContext(UserContext) }
2. 修改WelcomeChamado.js
无需自己维护用户状态,直接从Context取数据,避免和全局数据不同步:
import React, { useEffect, useState } from 'react' import { View, Image, ScrollView, StyleSheet, TouchableOpacity } from 'react-native' import { LinearGradient } from 'expo-linear-gradient'; import { initialWindowMetrics, SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context'; import NavBar from '../../../components/NavBar'; import { TextMuseo300, TextMuseo500 } from '../../../components/fonts/TextFonts'; import { theme } from '../../../global/theme'; import { TextInputMuseo300 } from '../../../components/fonts/TextInputFonts'; import AsyncStorage from '@react-native-async-storage/async-storage'; const { principalOne, principalTwo, principalThree, destaque, branco } = theme.colors; import { Entypo } from '@expo/vector-icons'; // 引入全局用户钩子 import { useUser } from '你的user.js对应路径' export function WelcomeChamado({ navigation }) { const insets = useSafeAreaInsets(); // 直接从全局Context取用户数据,无需自己维护状态 const { user } = useUser() const [foto, setFoto] = useState(''); useEffect(() => { const getInfo = async () => { const storagedFoto = await AsyncStorage.getItem('@lica:foto'); setFoto(storagedFoto); } getInfo(); }, []); const nextScreen = () => { navigation.navigate("AguardarChamado"); } // 原有渲染逻辑不变,直接使用user变量即可 return ( <ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={{ flexGrow: 1, }}> <SafeAreaProvider initialMetrics={initialWindowMetrics} style={ styles.container, { paddingTop: insets.top, paddingLeft: insets.left, paddingBottom: insets.bottom, paddingRight: insets.right, } }> <LinearGradient start={{ x: 0, y: 0.5 }} end={{ x: 1, y: 0.5 }} locations={[0, 0.2, 0.4, 0.6, 0.8, 1]} colors={['#8EF4F0', '#D6FDFA', '#F8FEFE', '#F8FEFE', '#D6FDFA', '#8EF4F0']} style={{ flex: 1 }}> <NavBar navigation={navigation} /> <View style={{ alignItems: 'center', marginTop: '5%' }}> <TextMuseo300 style={{ color: '#2E8F71', marginTop: '1%', fontWeight: 'bold', marginBottom: 10 }}>Olá</TextMuseo300> <View style={{ justifyContent: 'center', alignItems: 'center', }}> <Image source={{ uri: `data:image/gif;base64,${foto}` }} style={{ width: 100, height: 100, borderRadius: 50, borderWidth: 1, borderColor: destaque, }} resizeMode="cover" /> <View style={{ position: 'absolute', top: -5, right: 0, backgroundColor: '#2E8F71', width: 32, height: 32, borderRadius: 16, paddingLeft: 2, alignItems: 'center', justifyContent: 'center' }}> <Entypo name="bell" size={25} color="#FFF" /> </View> </View> <TextMuseo300 style={{ color: '#2E8F71', marginTop: '1%', fontWeight: 'bold', marginTop: 10 }}>{user?.CadastroNome}</TextMuseo300> <TextMuseo300 style={{ color: '#2E8F71', marginTop: '1%' }}>{user?.CadastroUser}</TextMuseo300> </View> <View style={{ marginTop: 30, alignItems: 'center', }}> <TextMuseo300 style={{ color: '#2E8F71', marginTop: '2%', maxWidth: 120, fontSize: 20, textAlign: 'center' }}>Você está disponível para receber chamados?</TextMuseo300> <TouchableOpacity onPress={nextScreen}> <View style={{ justifyContent: 'center', alignItems: 'center', backgroundColor: '#2E8F71', borderRadius: 10, width: 120, height: 30, marginTop: 50 }}> <TextMuseo300 style={{ color: '#FFF', marginTop: '2%', maxWidth: 120, fontSize: 20, fontWeight: 'bold', textAlign: 'center' }}>Sim, estou.</TextMuseo300> </View> </TouchableOpacity> </View> </LinearGradient> </SafeAreaProvider> </ScrollView> ); } const styles = StyleSheet.create({ container: { flex: 1, }, center: { flex: 1, alignItems: 'center', justifyContent: 'center', }, });
优化建议
- 优先用Context全局状态做渲染数据源,AsyncStorage仅作为持久化缓存使用,不要让业务组件直接读取Storage作为渲染源,避免数据不同步问题。
- 可以在APP启动时新增启动加载页,等用户核心数据加载完成后再渲染业务页面,避免出现字段空白的情况。
内容的提问来源于stack exchange,提问作者Renan Oliveira
相关产品推荐
相关产品推荐

