You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Expo React Native中Zustand状态在子组件更新延迟问题排查

问题原因分析

  1. Zustand状态更新逻辑错误:在updateDeviceSettings和loadSettings方法中,在set的回调函数内部重复调用set,违反了Zustand的使用规范,会导致状态更新的异步冲突,引发组件状态更新延迟。
  2. 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'] }]}>
                            &copy; {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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 17:02:33