ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

点赞动画可以先跑,接口状态必须能回滚

2026/8/14 15:28:23 拓冰建站 浏览量
点赞动画可以先跑,接口状态必须能回滚

点赞动画可以先跑,接口状态必须能回滚

点赞、收藏需要即时反馈,也必须面对网络延迟与连续点击。等接口成功再播动画会显得迟钝,忽略乱序响应则会让本地状态与服务端各说各话。

比较稳妥的做法是:前端先更新可回滚的本地状态,服务端提供幂等和版本语义,前端只接受仍然有效的响应。


先统一请求与状态版本语义

可以在开发者工具中模拟高延迟,再连续切换状态,观察请求与响应的顺序。

# 使用 curl 模拟网络抖动下的乱序微交互请求发送 curl -X POST http://localhost:8080/api/v1/like -H "Content-Type: application/json" -d '{"resource_id": "example", "sequence_id": 1}' curl -X POST http://localhost:8080/api/v1/unlike -H "Content-Type: application/json" -d '{"resource_id": "example", "sequence_id": 2}' # 抓取前端 WebSocket / 微交互请求的响应时序 tcpdump -i lo0 -X port 8080

重点不是假设某次请求必然乱序,而是验证发生乱序、失败或重试时,前端会不会错误地覆盖较新的状态。


乐观 UI 微交互与状态机回滚架构

乐观更新可以与请求版本、幂等键和回滚策略配合使用:


乐观 UI 状态机示例

前端可以把状态变更和网络确认分开处理。以下示例只保留最新一次操作的响应:

export interface MicroInteractionState { isLiked: boolean; likeCount: number; } export class OptimisticLikeManager { private currentState: MicroInteractionState; private sequenceCounter = 0; private pendingSequence: number | null = null; constructor( initialState: MicroInteractionState, private onStateRender: (state: MicroInteractionState, animate: boolean) => void, private apiSync: (isLiked: boolean, sequenceId: number) => Promise<boolean> ) { this.currentState = { ...initialState }; this.onStateRender(this.currentState, false); } public async toggleLike(): Promise<void> { // 1. 生成单调递增的微交互序列号 const currentSeq = ++this.sequenceCounter; this.pendingSequence = currentSeq; // 2. 备份快照供回滚使用 const rollbackState = { ...this.currentState }; // 3. 乐观 UI 更新:无需等待 API 响应,直接在 0 毫秒内计算最新 UI 状态 const nextIsLiked = !this.currentState.isLiked; this.currentState = { isLiked: nextIsLiked, likeCount: nextIsLiked ? this.currentState.likeCount + 1 : this.currentState.likeCount - 1, }; // 4. 触发微交互动画渲染 this.onStateRender(this.currentState, true); try { // 5. 异步同步给后端 API const success = await this.apiSync(nextIsLiked, currentSeq); // 检查当前是否有更新的微交互产生,若有则丢弃旧响应 if (this.pendingSequence !== currentSeq) return; if (!success) { throw new Error('API_SYNC_FAILED'); } } catch (err) { // 6. 网络异常触发确定性回滚 if (this.pendingSequence === currentSeq) { console.warn('[MicroInteraction] 接口响应失败,执行状态与动画反向回滚'); this.currentState = rollbackState; // 触发回滚动画(如红心破裂抖动) this.onStateRender(this.currentState, true); } } } }

接口设计时绝不返工的三项约定

和后端约定接口时,至少明确:

  • 请求如何标识和去重。递增序号、版本号或幂等键均可,关键是前后端对语义一致。
  • 成功响应返回服务端确认后的状态,前端以它作为下一次同步的基准。
  • 拖拽、滑块等高频输入在客户端节流或合并;是否需要批量接口取决于业务的提交语义。

微交互也需要失败设计

本地状态、网络确认和回滚说得清楚,微交互才可靠。动画可以先开始,失败时也要自然回到服务端确认状态,不能只顾那一下手感。