如何在iOS设备上重置react-redux持久化store以完成测试
问题梳理
背景
我已尝试查阅该问题的相关资料,但仍无法解释iOS设备上react-redux与redux-persist的store配置的实际运行情况。
问题描述
- QA团队无法测试应用的引导流程体验
- 引导流程逻辑:3个介绍页(由
isNew标志控制)→ 登录 → 引导教程(触发条件为is_first_time_login: true、hasLoggedIn || hasPinCodeLoggedIn: true且hasFinishedOnboardTutorial:false) - Android设备测试表现符合预期,iOS设备未正常生效
- iOS设备已尝试删除应用、重启手机后重新安装通过Firebase Distribution分发的测试包,问题仍未解决
相关代码配置
store/index.js
import { applyMiddleware, compose, createStore } from 'redux'; import { persistReducer, persistStore, createTransform } from 'redux-persist'; import createSagaMiddleware from 'redux-saga'; import logger from 'redux-logger'; import createSensitiveStorage from 'redux-persist-sensitive-storage'; import { NODE_ENV } from '@env'; import isEmpty from 'lodash/isEmpty'; import RootSaga from 'Sagas'; import RootReducer from 'Reducers'; import { UserDetail } from 'Models'; import ApiUtils from 'Services'; import { Configs, reactotron } from 'Configs'; const secureStorage = createSensitiveStorage({ keychainService: 'xxxx', sharedPreferencesName: 'xxx' }); const PersistStateTransformer = createTransform( (inboundState) => { return { ...inboundState }; }, (outboundState) => { if (!isEmpty(outboundState?.token)) { ApiUtils.injectToken(outboundState?.token); } return { ...outboundState, userDetails: new UserDetail(outboundState.userDetails), backgroundTimestamp: 0, hasPinCodeLoggedIn: false }; }, { whitelist: ['auth'], } ); const persistConfig = { key: 'root', storage: secureStorage, transforms: [PersistStateTransformer], whitelist: ['auth', 'ui'], }; // Use to remove all the existing state including auth/ui // if (NODE_ENV === 'development') { // AsyncStorage.removeItem('persist:root'); // } export const buildStore = (initialState = {}) => { const middlewares = []; const enhancers = []; // Connect the sagas to the redux store const sagaMiddleware = createSagaMiddleware(); middlewares.push(sagaMiddleware); if (NODE_ENV === 'development') { middlewares.push(logger); } // do not remove spread operator enhancers.push(applyMiddleware(...middlewares)); // Redux persist const persistedReducer = persistReducer(persistConfig, RootReducer); // Reactotron is only needed during dev, in test env it's not necessary const store = NODE_ENV === 'development' ? createStore(persistedReducer, initialState, compose(...enhancers, reactotron.createEnhancer())) : createStore(persistedReducer, initialState, compose(...enhancers)); const persistor = persistStore(store); // Kick off the root saga sagaMiddleware.run(RootSaga); return { store, persistor }; };
App.js
import React, { useEffect } from 'react'; import { Platform, UIManager } from 'react-native'; import { Provider } from 'react-redux'; import { PersistGate } from 'redux-persist/integration/react'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import SplashScreen from 'react-native-splash-screen'; import Navigation from 'Navigations'; import { buildStore } from 'Stores'; import { Loading } from 'Screens'; import { Toast } from 'Components'; import GlobalUiProvider from 'Contexts'; import ApiUtils from 'Services'; const { store, persistor } = buildStore(); ApiUtils.injectStore(store); const App = () => { if (Platform.OS === 'android') { UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true); } useEffect(() => { SplashScreen.hide(); }, []); return ( <SafeAreaProvider> <Provider store={store}> <PersistGate loading={<Loading />} persistor={persistor}> <GlobalUiProvider> <Navigation /> </GlobalUiProvider> <Toast /> </PersistGate> </Provider> </SafeAreaProvider> ); }; export default App; export { store, persistor };
publicNavigation.js(代码片段)
<PublicStack.Navigator screenOptions={Configs.PUBLIC_SCREEN_OPTIONS}> {isNew ? ( <PublicStack.Screen name="Onboard" component={Onboard} /> ) : ( <PublicStack.Screen name="Login" component={Login} /> )} </PublicStack.Navigator>
问题原因
该问题是iOS Keychain的系统特性导致:
你使用的redux-persist-sensitive-storage在iOS端基于系统Keychain实现,Keychain属于系统级存储,不属于App沙盒范畴,App卸载后对应的Keychain条目不会被系统自动清除,重装App后仍可读取到之前存储的auth、ui持久化状态,导致isNew、is_first_time_login等引导相关标志位保留旧值,无法触发引导流程。
而Android端对应的存储实现是SharedPreferences,属于App沙盒内存储,卸载App时会被同步清除,因此表现正常。
另外你代码中注释掉的AsyncStorage清除逻辑无效,因为你并未使用AsyncStorage作为持久化存储载体,删除AsyncStorage内容不会影响sensitive storage存储的状态。
解决方案
方案1:新增版本校验自动清空
在App启动时比对当前App版本号与持久化存储的版本号,若版本号不一致或无存储的版本号,直接调用persistor.purge()清空所有持久化内容,适配重装、升级场景。
示例逻辑:
// 在buildStore方法中新增逻辑,或在App启动的useEffect中添加 import { Version } from 'react-native'; const persistedVersion = store.getState().ui.appVersion; if (!persistedVersion || persistedVersion !== Version.appVersion) { persistor.purge().then(() => { // 清空后刷新状态 persistor.flush(); }); }
方案2:新增测试调试入口
针对QA测试场景,在App设置页添加隐藏的调试入口,点击后直接调用persistor.purge()重置所有持久化状态,无需重装App即可测试引导流程。
方案3:修改存储Key隔离版本/环境
修改persistConfig的key字段,添加版本号或环境前缀,不同版本/环境的存储Key不通用,重装或升级后无法读取旧版本存储内容,自动视为首次启动。
示例:
const persistConfig = { key: `root_v${Version.appVersion}`, // 追加版本号 storage: secureStorage, transforms: [PersistStateTransformer], whitelist: ['auth', 'ui'], };
内容的提问来源于stack exchange,提问作者mdoxb8
相关产品推荐
相关产品推荐

