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

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无法持久化:

  1. 存储类型不匹配
    useStorageState的setValue方法仅接受string | null类型,但你在signIn中传入的是sesionObj(对象类型)。直接存储对象会被隐式转为"[object Object]",读取时无法还原为有效数据,甚至可能在SecureStore中存储失败。

  2. 异步存储未等待完成
    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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 23:45:03