Expo React Native中Zustand状态在子组件更新延迟问题排查
问题原因分析
- Zustand状态更新逻辑错误:在
updateDeviceSettings和loadSettings方法中,在set的回调函数内部重复调用set,违反了Zustand的使用规范,会导致状态更新的异步冲突,引发组件状态更新延迟。 - Selector引用稳定性问题:SettingsScreen中使用的selector每次渲染都会创建新对象,可能导致Zustand无法正确跟踪状态变化,或引发不必要的重渲染。
修复步骤
1. 修正Zustand Store中的状态更新逻辑
将多次set调用合并为一次,直接返回完整的状态更新对象:
import { Appearance } from 'react-native' import { create } from 'zustand' import AsyncStorage from '@react-native-async-storage/async-storage' const useStore = create(set => ({ themes: { 'dark': { 'bg1': '#232323', 'bg2': '#000', 'bg3': '#444', 'c1': '#fff', 'c2': '#ccc', 'c3': '#999', 'map_fill': '#444' }, 'light': { 'bg1': '#EDF2F4', 'bg2': '#FFF', 'bg3': '#D9D9D9', 'c1': '#000', 'c2': '#777', 'c3': '#6c757d', 'map_fill': '#ced4da' } }, theme: 'light', darkMode: false, updateDarkMode: (value, device) => { set(state => ({ darkMode: value, theme: value ? 'dark' : 'light', deviceSettings: device ? false : state.deviceSettings })) }, deviceSettings: false, updateDeviceSettings: value => { try { set(state => { const update = { deviceSettings: value }; if(value){ const deviceValue = Appearance.getColorScheme() if(deviceValue !== null){ const darkModeValue = deviceValue === 'dark' update.darkMode = darkModeValue update.theme = deviceValue } } return update }) } catch (err) { console.error('Error updating device settings:', err) } }, loadSettings: async () => { try { const deviceSettingsValue = await AsyncStorage.getItem('deviceSettings') const parsedDeviceSettings = deviceSettingsValue ? JSON.parse(deviceSettingsValue) : false const update = { deviceSettings: parsedDeviceSettings } if(parsedDeviceSettings){ const deviceValue = Appearance.getColorScheme() if(deviceValue !== null){ update.darkMode = deviceValue === 'dark' update.theme = deviceValue } } else { const darkModeValue = await AsyncStorage.getItem('darkMode') if(darkModeValue !== null){ const parsedDarkMode = JSON.parse(darkModeValue) update.darkMode = parsedDarkMode update.theme = parsedDarkMode ? 'dark' : 'light' } } set(update) } catch (err){ console.error('Error fetching device settings value:', err) } }, })) export default useStore
2. 优化SettingsScreen的状态订阅
使用Zustand的shallow比较优化selector,避免每次渲染创建新对象导致的订阅异常:
import { useState } from 'react' import { StyleSheet, View, Switch, SafeAreaView, Text, Pressable } from 'react-native' import AppStyles from '../Styles/AppStyles' import useStore from '../store' import { shallow } from 'zustand/shallow' // 导入shallow比较工具 import InformationPages from '../Components/InformationPages' export default () => { // 使用shallow比较确保selector引用稳定 const { themes, theme, darkMode, updateDarkMode, deviceSettings, updateDeviceSettings } = useStore(state => ({ themes: state.themes, theme: state.theme, darkMode: state.darkMode, updateDarkMode: state.updateDarkMode, deviceSettings: state.deviceSettings, updateDeviceSettings: state.updateDeviceSettings }), shallow) const [visible, setVisible] = useState(false) const [page, setPage] = useState('') console.log(33, theme, darkMode) return ( <View style={[AppStyles.container, { backgroundColor: themes[theme]['bg1'] }]}> <InformationPages themes={themes} theme={theme} visible={visible} page={page} handleClose={() => setVisible(false)} /> <SafeAreaView style={{ flex: 1 }}> <View style={AppStyles.header}> <View style={AppStyles.headerTop}> <Text style={[AppStyles.headerTitle, { color: themes[theme]['c1'] }]}>Settings</Text> </View> </View> <View style={styles.content}> <View styles={styles.options}> <View style={styles.option}> <View style={styles.optionMain}> <Text style={[styles.optionTitle, { color: themes[theme]['c1'] }]}>Dark Mode</Text> <Switch value={darkMode} onValueChange={() => updateDarkMode(!darkMode, deviceSettings)} /> </View> </View> <View style={styles.option}> <View style={styles.optionMain}> <Text style={[styles.optionTitle, { color: themes[theme]['c1'] }]}>Use Device Settings</Text> <Switch value={deviceSettings} onValueChange={() => updateDeviceSettings(!deviceSettings)} /> </View> <Text style={[styles.optionText, { color: themes[theme]['c2'] }]}>Set Dark Mode to use the Light or Dark selection located in your device Display & Birghtness settings.</Text> </View> </View> <View style={[styles.footer, { borderTopColor: themes[theme]['bg3'] }]}> <View style={styles.footerLinks}> <Pressable onPress={() => { setPage('Terms & Conditions') setVisible(true) }} > <Text style={[styles.footerText, { color: themes[theme]['c2'] }]}>Terms & Conditions</Text> </Pressable> <Pressable onPress={() => { setPage('Privacy Policy') setVisible(true) }} > <Text style={[styles.footerText, { color: themes[theme]['c2'] }]}>Privacy Policy</Text> </Pressable> </View> <Text style={[styles.footerText, styles.footerMain, { color: themes[theme]['c1'] }]}> © {new Date().getFullYear()} Hoppin </Text> </View> </View> </SafeAreaView> </View> ) } const styles = StyleSheet.create({ content: { flex: 1, justifyContent: 'space-between', paddingHorizontal: 15, paddingBottom: 10 }, options: { paddingVertical: 15 }, option: { marginBottom: 15 }, optionMain: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, optionTitle: { fontSize: 14, fontWeight: 'bold', }, optionText: { fontSize: 14, fontWeight: '300', color: '#777', marginTop: 5 }, footer: { alignItems: 'center', paddingVertical: 20, borderTopColor: '#D9D9D9', borderTopWidth: 1, }, footerLinks: { flexDirection: 'row', justifyContent: 'center', alignContent: 'center' }, footerText: { fontSize: 14, fontWeight: '300', marginHorizontal: 10 }, footerMain: { fontWeight: 'bold', marginTop: 10 } })
修复说明
- 合并set调用:Zustand的
set回调应直接返回完整状态变更对象,嵌套调用set会打乱状态更新队列,导致组件状态延迟或不一致。 - shallow比较优化:默认情况下Zustand对selector返回值做引用比较,每次渲染创建新对象会触发不必要的重渲染,
shallow浅比较确保仅当实际状态值变化时才更新组件。
内容的提问来源于stack exchange,提问作者willmahoney
相关产品推荐
相关产品推荐

