rn_for_openharmony_取消收藏:和收藏功能的那些相同与不同
本文详细分析了取消收藏功能的两种实现场景及其技术细节: 文章列表页取消收藏使用文章原始ID调用/lg/uncollect_originId/{id}接口 收藏列表页取消收藏需使用收藏记录ID调用/lg/uncollect/{id}接口(项目中未实现) 关键点包括: 两种取消方式对应不同数据结构和接口 收藏状态切换通过item.collect布尔值控制 操作后通过回调通知父组件更新UI 状态同步问题

案例项目开源地址:https://atomgit.com/nutpi/wanandroid_rn_openharmony
上一篇讲了收藏功能,这一篇来聊取消收藏。你可能会想:不就是把收藏接口换成取消收藏接口吗?
没那么简单。取消收藏有两个场景,用的接口还不一样。
两个取消收藏的场景
场景一:在文章列表页,用户点击已收藏文章的红心,取消收藏。
场景二:在收藏列表页,用户点击某篇文章的红心,取消收藏。
看起来一样的操作,但背后的数据结构不同,需要调用不同的接口。
文章列表页的取消收藏
这是我们在 ArticleCard 组件里实现的:
const handleCollect = async () => {
if (!isLoggedIn) {
Alert.alert('提示', '请先登录');
return;
}
try {
if (item.collect) {
const res = await collectApi.uncollect(item.id);
if (res.errorCode === 0) {
Alert.alert('成功', '已取消收藏');
onCollectChange?.();
}
} else {
const res = await collectApi.collect(item.id);
if (res.errorCode === 0) {
Alert.alert('成功', '收藏成功');
onCollectChange?.();
}
}
} catch (e) {}
};
item.collect 的判断逻辑
if (item.collect) 这个判断是整个函数的分水岭。item.collect 是一个布尔值,来自接口返回的文章数据,表示当前用户是否已收藏这篇文章。
当 item.collect 为 true 时,说明用户已经收藏了这篇文章,再次点击就是要取消收藏,所以调用 collectApi.uncollect。
当 item.collect 为 false 时,说明用户还没收藏这篇文章,点击就是要收藏,所以调用 collectApi.collect。
这种设计让同一个按钮实现了"切换"功能:点一下收藏,再点一下取消,再点一下又收藏……
uncollect 接口的参数
uncollect: (id: number) => api.post(`/lg/uncollect_originId/${id}/json`, {}),
注意接口路径里有 originId 这个词。这里传的 item.id 是文章的原始 ID,不是收藏记录的 ID。
WanAndroid 的设计是这样的:每篇文章有一个固定的 ID(originId),当用户收藏这篇文章时,会创建一条收藏记录,这条记录有自己的 ID。在文章列表页,我们只有文章的原始 ID,所以用 uncollect_originId 接口。
为什么用 POST 而不是 DELETE
按照 RESTful 规范,删除操作应该用 DELETE 方法。但 WanAndroid 的接口用的是 POST。这是接口设计者的选择,我们作为调用方只能遵守。
实际上很多国内的接口都不严格遵循 RESTful,增删改查全用 POST 的也不少见。重要的是和后端约定好,保持一致。
收藏列表页的取消收藏
收藏列表页的数据结构和文章列表页不同:
collectArticles.slice(0, 5).map(item => (
<TouchableOpacity key={item.id} style={styles.collectItem} onPress={() => openLink(item.link)}>
<Text style={[styles.collectItemTitle, {color: theme.text}]} numberOfLines={1}>
{item.title.replace(/<[^>]+>/g, '')}
</Text>
<Text style={{color: theme.subText}}>›</Text>
</TouchableOpacity>
))
收藏列表的数据来源
const loadCollectList = async () => {
try {
const res = await collectApi.getList(0);
if (res.errorCode === 0) {
setCollectArticles(res.data.datas);
}
} catch (e) {}
};
collectApi.getList(0) 调用收藏列表接口,参数 0 是页码(从 0 开始)。返回的数据结构和普通文章列表类似,但有一个关键区别:这里的 item.id 是收藏记录的 ID,不是文章的原始 ID。
如果要在收藏列表页取消收藏
需要用另一个接口:
// 这个接口我们没有封装,但 WanAndroid 提供了
// POST /lg/uncollect/{id}/json
// 这里的 id 是收藏记录的 ID
收藏列表返回的数据里,item.id 是收藏记录 ID,item.originId 是文章原始 ID。取消收藏时要用收藏记录 ID。
我们项目的简化处理
我们的收藏列表只展示了标题,点击是跳转到文章链接,没有做取消收藏的功能。如果要加,需要:
- 给每个收藏项加一个删除按钮
- 点击时调用
/lg/uncollect/{id}/json接口,传收藏记录 ID - 成功后刷新收藏列表
两种取消收藏的对比
相同点
都是 POST 请求。
都需要登录状态(Cookie)。
成功后都返回 errorCode: 0。
不同点
接口路径不同:/lg/uncollect_originId/{id} vs /lg/uncollect/{id}。
参数含义不同:文章原始 ID vs 收藏记录 ID。
使用场景不同:文章列表页 vs 收藏列表页。
API 层的完整实现
export const collectApi = {
getList: (page: number) => api.get(`/lg/collect/list/${page}/json`),
collect: (id: number) => api.post(`/lg/collect/${id}/json`, {}),
uncollect: (id: number) => api.post(`/lg/uncollect_originId/${id}/json`, {}),
};
getList 接口
/lg/collect/list/${page}/json 获取收藏列表,分页加载。页码从 0 开始,每页返回 20 条数据(WanAndroid 的默认值)。
返回的数据结构:
{
"errorCode": 0,
"data": {
"curPage": 1,
"datas": [
{
"id": 123456,
"originId": 789,
"title": "文章标题",
"link": "https://...",
...
}
],
"pageCount": 5,
"size": 20
}
}
注意 datas 数组里每个对象都有 id(收藏记录 ID)和 originId(文章原始 ID)两个字段。
collect 接口
/lg/collect/${id}/json 收藏文章。这里的 id 是文章原始 ID。收藏成功后,服务端会创建一条收藏记录。
uncollect 接口
/lg/uncollect_originId/${id}/json 根据文章原始 ID 取消收藏。服务端会找到对应的收藏记录并删除。
取消收藏后的界面更新
if (res.errorCode === 0) {
Alert.alert('成功', '已取消收藏');
onCollectChange?.();
}
弹窗提示
Alert.alert('成功', '已取消收藏') 给用户一个明确的反馈。虽然心形图标会从红变白,但弹窗能让用户更确定操作成功了。
有些 App 会用 Toast 而不是 Alert,Toast 不需要用户点击确认,几秒后自动消失,侵入性更小。React Native 没有内置 Toast,需要用第三方库或自己实现。我们用 Alert 是为了简单。
回调通知父组件
onCollectChange?.() 通知父组件数据变了。父组件收到通知后会重新加载文章列表,新数据里这篇文章的 collect 字段就变成 false 了。
然后 React 重新渲染 ArticleCard 组件,item.collect 变成 false,心形图标从 ❤️ 变成 🤍。
可选链的必要性
onCollectChange?.() 里的 ?. 是可选链操作符。因为 onCollectChange 是可选的 prop,调用方可能没传。如果不用可选链,直接写 onCollectChange(),当它是 undefined 时会报错:TypeError: onCollectChange is not a function。
可选链会先检查 onCollectChange 是否存在,存在才调用,不存在就跳过。这是一种防御性编程。
收藏状态的同步问题
有一个细节问题:用户在首页收藏了一篇文章,然后切换到"我的"页面查看收藏列表,这篇文章会出现在列表里吗?
答案是:不一定。
因为收藏列表的数据是在页面加载时获取的,之后不会自动更新。用户在首页收藏文章后,收藏列表的数据还是旧的。
解决方案一:每次切换到"我的"页面时刷新
useEffect(() => {
if (isLoggedIn) {
loadCollectList();
}
}, [isLoggedIn]);
我们现在的实现是监听 isLoggedIn 变化,登录后加载一次。可以改成监听页面焦点:
import {useFocusEffect} from '@react-navigation/native';
useFocusEffect(
useCallback(() => {
if (isLoggedIn) {
loadCollectList();
}
}, [isLoggedIn])
);
每次页面获得焦点时都刷新收藏列表。
解决方案二:用全局状态管理
把收藏列表放到 Context 或 Redux 里,收藏/取消收藏时同步更新。这样不需要重新请求接口,但实现更复杂。
我们的选择
我们选择了简单方案:收藏列表有一个"刷新"按钮,用户可以手动刷新。
<TouchableOpacity onPress={loadCollectList}>
<Text style={{color: theme.accent}}>刷新</Text>
</TouchableOpacity>
不是最优解,但够用。
错误处理的考量
try {
if (item.collect) {
const res = await collectApi.uncollect(item.id);
if (res.errorCode === 0) {
Alert.alert('成功', '已取消收藏');
onCollectChange?.();
}
}
// ...
} catch (e) {}
catch 块为空的问题
现在的代码,网络错误时什么都不做。用户点了取消收藏,等了几秒,什么反应都没有,会很困惑。
更好的做法:
try {
// ...
} catch (e) {
Alert.alert('错误', '网络异常,请检查网络后重试');
}
errorCode 非 0 的处理
现在只处理了 errorCode === 0 的情况。如果接口返回错误(比如文章不存在、没有权限等),也是静默失败。
更好的做法:
if (res.errorCode === 0) {
Alert.alert('成功', '已取消收藏');
onCollectChange?.();
} else {
Alert.alert('失败', res.errorMsg || '取消收藏失败');
}
完整的取消收藏流程
- 用户点击红心图标
- 触发
handleCollect函数 - 检查登录状态,未登录则提示并返回
- 判断
item.collect为 true,走取消收藏分支 - 调用
collectApi.uncollect(item.id) - 等待接口返回
- 检查
errorCode,为 0 则成功 - 弹窗提示"已取消收藏"
- 调用
onCollectChange通知父组件 - 父组件重新加载数据
- 新数据传入 ArticleCard
- 心形图标从红变白
整个流程涉及用户交互、网络请求、状态更新、界面渲染,是一个完整的前端数据流闭环。
取消收藏和收藏是一对镜像操作,理解了一个,另一个也就懂了。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)