无Redux时React Navigator子屏调用父组件方法及设置页状态同步问题
Hey Adam, let's tackle this props syncing issue for your settings page— I’ve built similar setups with react-native-i18n, react-navigation, and AsyncStorage, so I know exactly where you might be hitting snags. Here’s a step-by-step solution that’ll get your preferences saving and syncing across the app smoothly.
Core Approach
The key is to pass an update callback from your root App.js to the Settings screen via react-navigation params. When the user changes settings, the Settings screen calls this callback, which updates App.js's state and persists the changes to AsyncStorage. This state change will trigger a re-render of your entire app, applying the new preferences immediately.
Step 1: Set Up App.js with State & AsyncStorage Logic
First, we’ll initialize your app’s settings state, load saved preferences on mount, and create a callback to update settings:
import React, { Component } from 'react'; import { AsyncStorage, View } from 'react-native'; import { NavigationContainer } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import I18n from 'react-native-i18n'; import SettingsScreen from './SettingsScreen'; // Import your other screens here const Stack = createStackNavigator(); class App extends Component { // Initialize default settings state = { settings: { language: 'auto', // Auto-detect by default darkMode: false // Example second setting } }; componentDidMount() { // Load saved settings when the app starts this.loadSavedSettings(); } loadSavedSettings = async () => { try { const savedSettings = await AsyncStorage.getItem('appSettings'); if (savedSettings) { const parsedSettings = JSON.parse(savedSettings); this.setState({ settings: parsedSettings }, () => { // Apply the saved language immediately this.setAppLanguage(parsedSettings.language); }); } } catch (error) { console.error('Failed to load settings:', error); } }; // Helper to apply language settings to react-native-i18n setAppLanguage = (language) => { if (language === 'auto') { I18n.locale = I18n.currentLocale(); } else { I18n.locale = language; } }; // Callback to update settings from the Settings screen updateAppSettings = async (newSettings) => { try { // Save to AsyncStorage first await AsyncStorage.setItem('appSettings', JSON.stringify(newSettings)); // Update App.js state to trigger app-wide re-render this.setState({ settings: newSettings }, () => { this.setAppLanguage(newSettings.language); // Optional: Force a full re-render if needed (rarely required) // this.forceUpdate(); }); } catch (error) { console.error('Failed to save settings:', error); } }; render() { return ( <NavigationContainer> <Stack.Navigator> <Stack.Screen name="Settings" component={SettingsScreen} // Pass the update callback and current settings as navigation params initialParams={{ updateAppSettings: this.updateAppSettings, currentSettings: this.state.settings }} /> {/* Add your other screens here */} </Stack.Navigator> </NavigationContainer> ); } } export default App;
Step 2: Build the Settings Screen with Prop Handling
Now, the Settings screen will receive the callback and current settings via navigation params. We’ll use react-native-popup-menu for language selection and a Switch for the dark mode example:
import React, { useState, useEffect } from 'react'; import { View, Text, TouchableOpacity, Switch, StyleSheet } from 'react-native'; import { Menu, MenuOptions, MenuOption, MenuTrigger } from 'react-native-popup-menu'; import I18n from 'react-native-i18n'; const SettingsScreen = ({ route }) => { // Extract the callback and current settings from navigation params const { updateAppSettings, currentSettings } = route.params; // Local state to track changes before syncing const [settings, setSettings] = useState(currentSettings); // Sync local state with App.js state if it updates (e.g., from another screen) useEffect(() => { setSettings(currentSettings); }, [currentSettings]); // Handle language selection from the popup menu const handleLanguageChange = (language) => { const newSettings = { ...settings, language }; setSettings(newSettings); // Call the callback to update App.js state and AsyncStorage updateAppSettings(newSettings); }; // Handle dark mode toggle const handleDarkModeToggle = () => { const newSettings = { ...settings, darkMode: !settings.darkMode }; setSettings(newSettings); updateAppSettings(newSettings); }; return ( <View style={styles.container}> <Text style={styles.title}>{I18n.t('settings.title')}</Text> {/* Language Selection Menu */} <View style={styles.settingSection}> <Text style={styles.settingLabel}>{I18n.t('settings.language')}</Text> <Menu> <MenuTrigger> <Text style={styles.triggerText}> {settings.language === 'auto' ? I18n.t('settings.auto') : settings.language === 'en' ? 'English' : 'Deutsch'} </Text> </MenuTrigger> <MenuOptions> <MenuOption onSelect={() => handleLanguageChange('auto')}> <Text>{I18n.t('settings.auto')}</Text> </MenuOption> <MenuOption onSelect={() => handleLanguageChange('en')}> <Text>English</Text> </MenuOption> <MenuOption onSelect={() => handleLanguageChange('de')}> <Text>Deutsch</Text> </MenuOption> </MenuOptions> </Menu> </View> {/* Dark Mode Toggle */} <View style={styles.settingRow}> <Text style={styles.settingLabel}>{I18n.t('settings.darkMode')}</Text> <Switch value={settings.darkMode} onValueChange={handleDarkModeToggle} /> </View> </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, padding: 20, backgroundColor: '#fff' }, title: { fontSize: 20, fontWeight: 'bold', marginBottom: 30 }, settingSection: { marginBottom: 30 }, settingLabel: { fontSize: 16, marginBottom: 10 }, triggerText: { color: '#007AFF', fontSize: 16 }, settingRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' } }); export default SettingsScreen;
Fixing Common Prop Issues
If you’re still having trouble with props:
- Class Component Settings Screen: If you’re using a class component instead of functional, access params via
this.props.route.params.updateAppSettings(make sure to bind methods or use arrow functions). - Nested Navigation: If your Settings screen is inside a TabNavigator or nested stack, you don’t need to pass props through every level— just use
useRoute()(functional) orthis.props.route(class) to access params directly. - State Sync: The
useEffectin the Settings screen ensures local state stays in sync withApp.js’s state, so if settings are updated elsewhere, the screen will reflect changes immediately.
Post-Eject Notes
When you eject from Expo, you’ll want to replace the deprecated AsyncStorage with @react-native-async-storage/async-storage— the API is nearly identical, so you’ll just need to update your imports.
内容的提问来源于stack exchange,提问作者Adam

