React Native使用Zustand条件渲染时UI闪烁问题求助
如何在React Native条件渲染中避免UI闪烁(Zustand vs Redux差异问题)
我在React Native中使用Zustand进行条件渲染时遇到了UI闪烁问题,但用Redux实现相同逻辑却没有这个问题。以下是我的代码,已经尝试过Stack Overflow相关方案但无效,求解决办法。
代码示例
App.tsx
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; import { NavigationContainer } from "@react-navigation/native"; import { createStackNavigator } from "@react-navigation/stack"; import { MytabBarone } from "@src/navigations/BottomNavigation/MytabBarone"; import { HomeTabParamList, RootStackParamList } from "@src/navigations/type"; import HomeScreen from "@src/screens/Home/HomeScreen"; import Login from "@src/screens/Login/Login"; import OnBoarding from "@src/screens/OnBoardingScreen/OnBoardingScreen"; import { useAppLaunchStore } from "@src/store/AppLaunch"; import { StyleSheet } from "react-native"; import "react-native-gesture-handler"; const Stack = createStackNavigator<RootStackParamList>(); const BottomTab = createBottomTabNavigator<HomeTabParamList>(); export default function App() { const isAppLaunchFirst = useAppLaunchStore((state) => state.isAppLaunchFirst); console.log(isAppLaunchFirst); const isAuth = false; return ( <> <NavigationContainer> <Stack.Navigator screenOptions={{ headerShown: false }}> {isAuth && ( <Stack.Screen name='Appartment' component={AppartmentBottomTab} /> )} {isAppLaunchFirst ? ( <Stack.Screen name='OnBoarding' component={OnBoarding} /> ) : null} <Stack.Screen name='Login' component={Login} /> </Stack.Navigator> </NavigationContainer> </> ); } const ApartmentBottomTab = () => { return ( <BottomTab.Navigator tabBar={(props) => <MytabBarone {...props} />} initialRouteName={"Home"} screenOptions={{ headerShown: false }} > <BottomTab.Screen name='Home' component={HomeScreen} /> </BottomTab.Navigator> ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#fff", alignItems: "center", justifyContent: "center", }, });
OnBoarding组件
const OnBoarding = ({ navigation }: RootStackScreenProps<"OnBoarding">) => { const setIsAppLaunchFirst = useAppLaunchStore( (state) => state.setIsAppLaunchFirst ); return ( <View style={styles.container}> <TouchableOpacity style={{ height: 50, width: "50%", backgroundColor: COLORS.blue, borderRadius: 5, justifyContent: "center", position: "absolute", bottom: 120, }} onPress={() => { setIsAppLaunchFirst(false); navigation.navigate("Login"); }} > <Text style={{ textAlign: "center", color: COLORS.white }}> COMMENCER </Text> </TouchableOpacity> </View> ); };
Zustand Store
import { deleteItemAsync, getItemAsync, setItemAsync } from "expo-secure-store"; import { create } from "zustand"; import { PersistStorage, persist } from "zustand/middleware"; interface AppLaunchType { isAppLaunchFirst: boolean | null; setIsAppLaunchFirst: (value: boolean) => void; } const storage: PersistStorage<AppLaunchType> = { setItem: async (key, value) => { await setItemAsync(key, JSON.stringify(value)); }, getItem: async (name) => { const value = await getItemAsync(name); return value !== null ? JSON.parse(value) : undefined; }, removeItem: async (key) => { await deleteItemAsync(key); }, }; export const useAppLaunchStore = create( persist<AppLaunchType>( (set) => ({ isAppLaunchFirst: true, setIsAppLaunchFirst: (value) => { set(() => ({ isAppLaunchFirst: value })); }, }), { name: "AppFirstLaunch", storage, } ) );
问题原因与解决方案
核心原因
Zustand的persist中间件初始化时是异步读取存储数据的,组件会先拿到默认值true,等异步读取完成后才会更新为存储的实际值(比如首次启动后是false)。这种状态的两次切换会触发组件重复渲染,进而导致UI闪烁。而Redux的持久化方案通常会在应用启动时等待异步缓存读取完成后再渲染根组件,因此不会出现该问题。
具体解决步骤
1. 等待persist初始化完成后再渲染UI
在Zustand Store中添加初始化状态标记,确保UI只在缓存读取完成后渲染:
修改Store:
interface AppLaunchType { isAppLaunchFirst: boolean | null; setIsAppLaunchFirst: (value: boolean) => void; isInitialized: boolean; // 新增初始化完成标记 } export const useAppLaunchStore = create( persist<AppLaunchType>( (set) => ({ isAppLaunchFirst: true, isInitialized: false, // 默认未初始化 setIsAppLaunchFirst: (value) => { set(() => ({ isAppLaunchFirst: value })); }, }), { name: "AppFirstLaunch", storage, onRehydrateStorage: () => { return (state) => { set({ isInitialized: true }); // 缓存恢复完成后标记初始化完成 }; }, } ) );
修改App组件,添加加载过渡:
import { ActivityIndicator, View } from "react-native"; export default function App() { const { isAppLaunchFirst, isInitialized } = useAppLaunchStore((state) => ({ isAppLaunchFirst: state.isAppLaunchFirst, isInitialized: state.isInitialized, })); const isAuth = false; // 未初始化时显示加载占位 if (!isInitialized) { return ( <View style={styles.container}> <ActivityIndicator size="large" color="#0000ff" /> </View> ); } return ( <> <NavigationContainer> <Stack.Navigator screenOptions={{ headerShown: false }}> {isAuth && ( <Stack.Screen name='Appartment' component={AppartmentBottomTab} /> )} {isAppLaunchFirst ? ( <Stack.Screen name='OnBoarding' component={OnBoarding} /> ) : null} <Stack.Screen name='Login' component={Login} /> </Stack.Navigator> </NavigationContainer> </> ); }
2. 优化条件渲染逻辑,避免重复挂载组件
当前代码中OnBoarding和Login屏幕同时存在于栈中,只是根据条件显示,这会导致两个组件都被初始化,切换时容易出现闪烁。建议改为动态设置初始路由:
export default function App() { const { isAppLaunchFirst, isInitialized } = useAppLaunchStore((state) => ({ isAppLaunchFirst: state.isAppLaunchFirst, isInitialized: state.isInitialized, })); const isAuth = false; if (!isInitialized) { return ( <View style={styles.container}> <ActivityIndicator size="large" color="#0000ff" /> </View> ); } // 动态确定初始路由 let initialRouteName = 'Login'; if (isAuth) { initialRouteName = 'Appartment'; } else if (isAppLaunchFirst) { initialRouteName = 'OnBoarding'; } return ( <> <NavigationContainer> <Stack.Navigator initialRouteName={initialRouteName} screenOptions={{ headerShown: false }} > <Stack.Screen name='Appartment' component={AppartmentBottomTab} /> <Stack.Screen name='OnBoarding' component={OnBoarding} /> <Stack.Screen name='Login' component={Login} /> </Stack.Navigator> </NavigationContainer> </> ); }
3. 优化存储读取性能
如果expo-secure-store读取速度过慢,可以考虑改用AsyncStorage(无需加密场景),或者提前在App启动时读取存储值:
// 提前读取初始状态 const getInitialState = async () => { const storedValue = await getItemAsync("AppFirstLaunch"); return storedValue ? JSON.parse(storedValue) : { isAppLaunchFirst: true }; }; // 在Store中使用初始状态 export const useAppLaunchStore = create( persist<AppLaunchType>( (set) => ({ ...(await getInitialState()), isInitialized: false, setIsAppLaunchFirst: (value) => { set(() => ({ isAppLaunchFirst: value })); }, }), { name: "AppFirstLaunch", storage, onRehydrateStorage: () => { return (state) => { set({ isInitialized: true }); }; }, } ) );
内容的提问来源于stack exchange,提问作者Kouma Ibrahim
相关产品推荐
相关产品推荐

