Expo重载/退出App后React Native会话丢失,求排查实现问题
问题描述
按照Expo官方认证教程实现功能,在ctx文件夹下创建了两个文件:
ctx/ctx.ts
import { useContext, createContext, type PropsWithChildren } from 'react'; import { useStorageState } from './useStorageState'; import {userSession} from '@/design/objectType' const AuthContext = createContext<{ signIn: (sesionObj:any) => void; signOut: () => void; session?: string | null; isLoading: boolean; }>({ signIn: () => null, signOut: () => null, session: null, isLoading: false, }); // This hook can be used to access the user info. export function useSession() { const value = useContext(AuthContext); if (process.env.NODE_ENV !== 'production') { if (!value) { throw new Error('useSession must be wrapped in a <SessionProvider />'); } } return value; } export function SessionProvider({ children }: PropsWithChildren) { const [[isLoading, session], setSession] = useStorageState('session'); return ( <AuthContext.Provider value={{ signIn: (sesionObj:any) => { // Perform sign-in logic here console.log(sesionObj); setSession(sesionObj); }, signOut: () => { setSession(null); }, session, isLoading, }}> {children} </AuthContext.Provider> ); }
useStorageState.ts
import { useEffect, useCallback, useReducer } from 'react'; import * as SecureStore from 'expo-secure-store'; import { Platform } from 'react-native'; type UseStateHook<T> = [[boolean, T | null], (value: T | null) => void]; function useAsyncState<T>( initialValue: [boolean, T | null] = [true, null], ): UseStateHook<T> { return useReducer( (state: [boolean, T | null], action: T | null = null): [boolean, T | null] => [false, action], initialValue ) as UseStateHook<T>; } export async function setStorageItemAsync(key: string, value: string | null) { if (Platform.OS === 'web') { try { if (value === null) { localStorage.removeItem(key); } else { localStorage.setItem(key, value); } } catch (e) { console.error('Local storage is unavailable:', e); } } else { if (value == null) { await SecureStore.deleteItemAsync(key); } else { await SecureStore.setItemAsync(key, value); } } } export function useStorageState(key: string): UseStateHook<string> { // Public const [state, setState] = useAsyncState<string>(); // Get useEffect(() => { if (Platform.OS === 'web') { try { if (typeof localStorage !== 'undefined') { setState(localStorage.getItem(key)); } } catch (e) { console.error('Local storage is unavailable:', e); } } else { SecureStore.getItemAsync(key).then(value => { setState(value); }); } }, [key]); // Set const setValue = useCallback( (value: string | null) => { setState(value); setStorageItemAsync(key, value); }, [key] ); return [state, setValue]; }
当前问题:每次退出App后,获取session总是返回null,请问实现是否存在错误?
问题原因及修复方案
你的代码存在两个核心问题,导致session无法持久化:
存储类型不匹配
useStorageState的setValue方法仅接受string | null类型,但你在signIn中传入的是sesionObj(对象类型)。直接存储对象会被隐式转为"[object Object]",读取时无法还原为有效数据,甚至可能在SecureStore中存储失败。异步存储未等待完成
setStorageItemAsync是异步函数,但调用时未使用await,可能导致App退出时存储操作还未完成,数据未被持久化。
修复步骤:
1. 将对象序列化为JSON字符串存储
修改ctx.ts中SessionProvider的signIn逻辑:
signIn: (sesionObj: userSession) => { console.log(sesionObj); // 将对象转为JSON字符串后存储 setSession(JSON.stringify(sesionObj)); },
2. 读取时解析JSON为对象
在ctx.ts中,将存储的字符串还原为对象,并更新类型定义:
// 更新AuthContext类型定义 const AuthContext = createContext<{ signIn: (sesionObj: userSession) => void; signOut: () => void; session?: userSession | null; isLoading: boolean; }>({ signIn: () => null, signOut: () => null, session: null, isLoading: false, }); // 更新SessionProvider中的session处理 export function SessionProvider({ children }: PropsWithChildren) { const [[isLoading, sessionStr], setSession] = useStorageState('session'); // 解析存储的JSON字符串为对象,处理null情况 const session = sessionStr ? JSON.parse(sessionStr) : null; return ( <AuthContext.Provider value={{ signIn: (sesionObj: userSession) => { setSession(JSON.stringify(sesionObj)); }, signOut: () => { setSession(null); }, session, isLoading, }}> {children} </AuthContext.Provider> ); }
3. 确保异步存储操作完成
修改useStorageState中的setValue方法,等待存储操作完成:
const setValue = useCallback( async (value: string | null) => { setState(value); await setStorageItemAsync(key, value); }, [key] );
4. 检查iOS权限配置(可选)
如果是iOS平台,确保在app.json中添加SecureStore相关权限:
{ "ios": { "infoPlist": { "NSFaceIDUsageDescription": "需要使用身份验证来访问存储的会话信息" } } }
完成以上修改后,session应该能在App重启后正常读取。
内容的提问来源于stack exchange,提问作者Ryan Arief
相关产品推荐
相关产品推荐

