rn_for_openharmony_退出登录:从点击按钮到状态清空的完整链路

案例项目开源地址:https://atomgit.com/nutpi/wanandroid_rn_openharmony
退出登录看起来是个简单功能,点一下按钮就完事了。但仔细想想,这个"点一下"背后要做的事情还挺多的:调接口、清 Cookie、重置状态、刷新界面、给用户反馈……
今天就来把这个流程拆开看看。
用户点击退出按钮
故事从这里开始:
<TouchableOpacity style={[styles.logoutBtn, {borderColor: theme.danger}]} onPress={logout}>
<Text style={{color: theme.danger}}>退出</Text>
</TouchableOpacity>
用户点击按钮,触发 onPress 回调,调用 logout 函数。
这个 logout 从哪来?从 AuthContext:
const {isLoggedIn, userInfo, logout} = useAuth();
useAuth 是我们自定义的 Hook,返回认证相关的状态和方法。
logout 函数的实现
const logout = async () => {
try {
await userApi.logout();
clearAuthCookie();
setIsLoggedIn(false);
setUserInfo(null);
Alert.alert('成功', '已退出登录');
} catch (e) {}
};
五行代码,五件事情:
- 调用登出接口
- 清除本地 Cookie
- 设置登录状态为 false
- 清空用户信息
- 弹窗提示用户
我们一个一个看。
第一步:调用登出接口
await userApi.logout();
userApi.logout 的实现:
export const userApi = {
logout: () => api.get('/user/logout/json'),
};
就是发一个 GET 请求到 /user/logout/json。
为什么要调接口
有人可能会问:直接清除本地状态不就行了,为什么还要调接口?
因为服务端也维护着登录状态。WanAndroid 用 Cookie 来识别用户,服务端会记录哪些 Cookie 是有效的。调用登出接口,服务端会把这个 Cookie 标记为失效。
如果不调接口,本地是退出了,但 Cookie 还是有效的。万一 Cookie 泄露,别人拿着这个 Cookie 还能冒充你操作。
await 的作用
await 等待接口返回。虽然我们没有用返回值,但等待是有意义的:确保服务端处理完成后再进行下一步。
不过这里有个问题:如果网络不好,接口超时了怎么办?用户要等很久才能退出?
实际上,即使接口失败,后面的清理逻辑还是会执行(因为我们没有在 catch 里 return)。所以用户体验上不会卡住。只是服务端的 Cookie 可能没有及时失效,但这个影响不大,Cookie 本身也有过期时间。
第二步:清除本地 Cookie
clearAuthCookie();
这个函数在 api.ts 里定义:
let authCookie = '';
export const clearAuthCookie = () => {
authCookie = '';
};
我们用一个模块级变量 authCookie 存储登录后获取的 Cookie。清除就是把它设为空字符串。
Cookie 是怎么来的
登录成功时,服务端会在响应头里返回 Set-Cookie:
export const api = {
async post(url: string, data: Record<string, string> = {}) {
const res = await fetch(`${BASE_URL}${url}`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
...(authCookie ? {Cookie: authCookie} : {}),
},
body: formData.toString(),
});
const setCookie = res.headers.get('set-cookie');
if (setCookie) {
authCookie = setCookie;
}
return res.json();
},
};
登录请求返回时,我们从响应头取出 Cookie 并保存。后续请求会在请求头里带上这个 Cookie。
为什么不用 AsyncStorage
有人可能会问:为什么不把 Cookie 存到 AsyncStorage 里,这样关闭 App 再打开还能保持登录?
可以这么做,但我们这个项目选择了简单方案:每次打开 App 都需要重新登录。这样实现简单,也更安全。
如果要实现"记住登录状态",需要:
- 登录成功后把 Cookie 存到 AsyncStorage
- App 启动时从 AsyncStorage 读取 Cookie
- 退出登录时从 AsyncStorage 删除 Cookie
- 处理 Cookie 过期的情况
第三步:设置登录状态为 false
setIsLoggedIn(false);
isLoggedIn 是 AuthContext 里的状态:
const [isLoggedIn, setIsLoggedIn] = useState(false);
设为 false 后,所有使用 isLoggedIn 的组件都会重新渲染。
哪些地方用到了 isLoggedIn
用户信息卡片:
{isLoggedIn ? (
<View style={styles.userInfo}>...</View>
) : (
<TouchableOpacity onPress={onShowLogin}>
<Text>点击登录</Text>
</TouchableOpacity>
)}
isLoggedIn 变成 false 后,用户信息区域会变成"点击登录"。
收藏按钮:
const handleCollect = async () => {
if (!isLoggedIn) {
Alert.alert('提示', '请先登录');
return;
}
// ...
};
退出后点击收藏,会提示"请先登录"。
收藏列表:
useEffect(() => {
if (isLoggedIn) {
loadCollectList();
}
}, [isLoggedIn]);
isLoggedIn 变化会触发这个 effect。变成 false 时不会加载收藏列表。
第四步:清空用户信息
setUserInfo(null);
userInfo 存储用户的详细信息:
interface UserInfo {
id: number;
username: string;
nickname: string;
coinCount: number;
level: number;
rank: string;
}
设为 null 后,用户信息卡片的头像会变成问号,用户名和积分也不再显示。
为什么要单独清空 userInfo
理论上,isLoggedIn 为 false 时,界面上就不会显示用户信息了。但清空 userInfo 还是有必要的:
- 释放内存,虽然用户信息很小,但养成好习惯
- 下次登录时,不会短暂显示上一个用户的信息
- 代码逻辑更清晰,登出就是"什么都没有"的状态
第五步:弹窗提示
Alert.alert('成功', '已退出登录');
给用户一个明确的反馈:操作完成了。
为什么需要提示
退出登录后,界面会变化(用户信息变成"点击登录"),用户能看到变化。那还需要弹窗吗?
需要。因为:
- 界面变化可能不够明显,用户可能没注意到
- 弹窗是一个明确的"句号",告诉用户"这件事做完了"
- 如果退出失败,也可以通过弹窗告诉用户
Alert.alert 的参数
Alert.alert(title, message, buttons, options);
我们只用了前两个参数:标题和内容。
如果想加按钮,可以这样:
Alert.alert(
'确认退出',
'确定要退出登录吗?',
[
{text: '取消', style: 'cancel'},
{text: '确定', onPress: () => doLogout()},
]
);
不过我们的退出按钮已经很明确了,不需要二次确认。
错误处理
const logout = async () => {
try {
await userApi.logout();
clearAuthCookie();
setIsLoggedIn(false);
setUserInfo(null);
Alert.alert('成功', '已退出登录');
} catch (e) {}
};
catch 块是空的,意味着即使接口调用失败,也不会有任何提示。
这样设计是有意的:退出登录是用户主动的操作,即使服务端没有正确处理,本地状态也应该清除。用户的意图是"我要退出",我们应该尊重这个意图。
如果想更严谨一些,可以这样:
const logout = async () => {
try {
await userApi.logout();
} catch (e) {
console.warn('登出接口调用失败', e);
} finally {
clearAuthCookie();
setIsLoggedIn(false);
setUserInfo(null);
Alert.alert('成功', '已退出登录');
}
};
用 finally 确保清理逻辑一定会执行,同时在 catch 里记录日志方便排查问题。
完整的 AuthContext 代码
import React, {createContext, useContext, useState, ReactNode} from 'react';
import {Alert} from 'react-native';
import {UserInfo} from '../types';
import {userApi, clearAuthCookie} from '../services/api';
interface AuthContextType {
isLoggedIn: boolean;
userInfo: UserInfo | null;
login: (username: string, password: string) => Promise<boolean>;
register: (username: string, password: string, repassword: string) => Promise<boolean>;
logout: () => Promise<void>;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export const AuthProvider = ({children}: {children: ReactNode}) => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
const login = async (username: string, password: string): Promise<boolean> => {
try {
const res = await userApi.login(username, password);
if (res.errorCode === 0) {
setIsLoggedIn(true);
setUserInfo(res.data);
return true;
} else {
Alert.alert('登录失败', res.errorMsg || '请检查用户名和密码');
return false;
}
} catch (e) {
Alert.alert('错误', '网络错误');
return false;
}
};
const register = async (username: string, password: string, repassword: string): Promise<boolean> => {
try {
const res = await userApi.register(username, password, repassword);
if (res.errorCode === 0) {
setIsLoggedIn(true);
setUserInfo(res.data);
return true;
} else {
Alert.alert('注册失败', res.errorMsg || '请检查输入信息');
return false;
}
} catch (e) {
Alert.alert('错误', '网络错误');
return false;
}
};
const logout = async () => {
try {
await userApi.logout();
clearAuthCookie();
setIsLoggedIn(false);
setUserInfo(null);
Alert.alert('成功', '已退出登录');
} catch (e) {}
};
return (
<AuthContext.Provider value={{isLoggedIn, userInfo, login, register, logout}}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
};
退出按钮的样式
logoutBtn: {
paddingHorizontal: 16,
paddingVertical: 8,
borderRadius: 8,
borderWidth: 1
},
用边框样式而不是填充背景,看起来更轻量。配合 theme.danger(红色),暗示这是一个需要谨慎的操作。
流程回顾
用户点击退出按钮 → 触发 logout 函数 → 调用登出接口 → 清除本地 Cookie → 设置 isLoggedIn 为 false → 清空 userInfo → 弹窗提示 → 界面自动更新
整个流程是同步的(虽然有 await,但用户感知上是瞬间完成的)。状态变化后,React 会自动重新渲染相关组件,用户看到的界面就从"已登录"变成了"未登录"。
这就是 React 的魅力:你只需要关心状态,界面会自动跟着变。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)