收藏与书签模式
让用户稍后保存项,含乐观 UI、持久化、空状态。
UX 模式入门
为什么
用户找到想重访的项但不想再搜。收藏给他们持久 '已存' 列表 — 高参与特性,便宜建且离线工作。
Nielsen Norman Group 研究显示用户在离开前平均停留 10-15 秒。清晰的视觉层次和渐进式披露将任务完成率提高 40-60%。
何时使用
任何有用户可能重访可浏览内容的应用:产品、文章、房产、工作、搜索结果。跳过一次性内容(收据、确认)。
如何做
1. 切换 UI — 星/心图标、乐观翻转、填充 vs 轮廓状态
2. 存储 — 用户作用域项 ID 列表;持久到后端(若登录)+ localStorage(离线)
3. 同步 — 登录时合并本地收藏与服务器;切换时写两者
4. 列表视图 — /favorites 页显已存项,按存日期排
5. 空状态 — "尚无收藏 — 点任何项的 ☆ 保存" 含浏览 CTA
6. 限制 — 上限 1000 收藏;超则建议文件夹/集合
6. 导出 — 让用户导出收藏(CSV/JSON)使其不感锁定
7. 移除 — 从列表视图(滑动或垃圾桶图标)取消收藏,非仅项页
示例
// useFavorites — 乐观切换、双存储
function useFavorites(userId?: string) {
const [ids, setIds] = useState<Set<string>>(() => loadLocal());
const toggle = (id: string) => {
const next = new Set(ids);
next.has(id) ? next.delete(id) : next.add(id); // 乐观
setIds(next); saveLocal(next);
if (userId) api.put(`/users/${userId}/favorites`, { ids: [...next] }).catch(() => setIds(ids)); // 回滚
};
return { ids, toggle, isFav: (id) => ids.has(id) };
}
// 星按钮 — 乐观、可访问
<button
aria-pressed={isFav(id)}
aria-label={isFav(id) ? "从收藏移除" : "加到收藏"}
onClick={() => toggle(id)}
>
<StarIcon filled={isFav(id)} />
</button>
// 空状态
function FavoritesEmpty() {
return (
<div>
<h2>尚无收藏</h2>
<p>点任何项的 ☆ 稍后保存。</p>
<Link to="/browse">浏览项</Link>
</div>
);
}
常见问题
- ⚠无离线存储 — 登出或断网时收藏消失,用户感背叛
- ⚠空状态仅 '无收藏' — 无指引,用户不知如何加项
- ⚠登录无合并 — 本地收藏被服务器覆写或反之,项丢
最佳实践
- ✓双存储(localStorage + 服务器)— 离线工作、登录同步、跨设备存活
- ✓乐观切换含回滚 — 即时反馈,服务器失败时回退
- ✓设计有用空状态 — 解释如何加收藏、链到浏览