如何不使用localStorage持久化Redux用户数据防止刷新丢失
Redux用户态持久化落地方案(适配GraphQL/Apollo + Redux Toolkit + React栈)
核心实现逻辑
Redux本身是内存级状态管理,页面刷新后状态清空是默认行为,不需要把敏感数据存在localStorage,也不用在每个业务组件重复发请求拉取用户数据,只需要在应用入口统一做一次初始化鉴权+数据拉取即可。
方案一:应用根层统一初始化用户态(首推)
- 在应用最外层(根组件/路由守卫层)加单次执行的初始化逻辑:页面首次加载时,先校验localStorage中存储的JWT是否存在、是否过期
- 若JWT合法,就解析JWT中携带的用户ID,发起一次GraphQL请求拉取当前登录用户的必要字段(用户名、邮箱、收货地址等,注意:任何场景下前端都不要存储、也不要要求服务端返回用户密码字段)
- 请求成功后直接dispatch将用户数据写入Redux Store,后续所有业务组件直接从Store取数即可,不需要重复发起请求
- 若JWT无效、过期或者请求返回未授权,直接清空localStorage中的无效JWT,重置Redux中的用户态为未登录状态,跳转至登录页即可
- 配合Apollo Client自带的缓存能力,可以给这个用户信息查询设置合理的缓存规则,同一次会话内不会重复发起请求,性能损耗可以忽略
参考实现代码:
// 应用根组件 App.jsx import { useEffect } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useLazyQueryGetCurrentUser } from '@/graphql/generated' // 基于GraphQL schema自动生成的查询hook import { setUserInfo, clearUserState } from '@/store/slices/user' import { jwtDecode } from 'jwt-decode' export default function App() { const dispatch = useDispatch() const [getCurrentUser] = useLazyQueryGetCurrentUser() const { userInfo } = useSelector(state => state.user) useEffect(() => { const initAppAuth = async () => { const accessToken = localStorage.getItem('access_token') if (!accessToken) { dispatch(clearUserState()) return } try { // 本地先校验JWT有效期,避免发送无效请求 const decodedToken = jwtDecode(accessToken) if (decodedToken.exp * 1000 < Date.now()) throw new Error('Token expired') // 拉取当前用户全量信息 const { data } = await getCurrentUser({ variables: { userId: decodedToken.sub } }) if (data?.currentUser) { dispatch(setUserInfo(data.currentUser)) } } catch (err) { localStorage.removeItem('access_token') dispatch(clearUserState()) } } // 仅Store中无用户信息时执行一次初始化 if (!userInfo) initAppAuth() }, []) // 路由渲染逻辑,可配合全局loading态避免初始化阶段页面闪烁 return ( <div id="app"> {/* 路由出口 */} </div> ) }
方案二:Redux局部持久化(搭配方案一优化体验)
如果担心初始化请求返回前页面出现登录态闪烁,可以用redux-persist做Redux的局部持久化,只持久化非敏感的基础用户字段,敏感字段仍然只存在内存中:
// Redux Store 配置文件 import { configureStore } from '@reduxjs/toolkit' import { persistStore, persistReducer } from 'redux-persist' import storage from 'redux-persist/lib/storage' import userReducer from './slices/user' // 用户态持久化配置 const userPersistConfig = { key: 'user_state', storage, // 白名单:仅持久化非敏感的基础字段,刷新后不会丢失 whitelist: ['userId', 'username', 'createdAt'], // 敏感字段(邮箱、收货地址等)不加入白名单,仅存在内存中,刷新后通过初始化请求重新拉取 } const persistedUserReducer = persistReducer(userPersistConfig, userReducer) export const store = configureStore({ reducer: { user: persistedUserReducer, // 其他业务reducer }, middleware: getDefaultMiddleware => getDefaultMiddleware({ serializableCheck: false // 忽略redux-persist相关action的序列化校验 }) }) export const persistor = persistStore(store)
这种实现下,页面刷新后可以先读取持久化的基础用户信息做初步渲染,等全量用户信息请求返回后再更新Store,体验更流畅,也不会泄露敏感数据。
注意事项
- JWT的定位是身份校验凭证,不要把用户敏感信息编码到JWT payload中,也不要把JWT当做数据存储载体
- 路由层可以加统一的鉴权守卫,用户态初始化完成前展示全局加载态,避免业务组件读取不到用户信息报错
- 不需要在每个业务组件单独发起用户信息请求,根层一次初始化+Apollo缓存+Redux全局状态完全可以覆盖所有取数场景,不会出现代码冗余的问题
内容的提问来源于stack exchange,提问作者עמית שוקרון
相关产品推荐
相关产品推荐

