卡拉OK报名应用SWR乐观UI实现异常求助
问题解决:SWR乐观UI实现优化
核心问题分析
你遇到的歌曲先消失再出现的问题,主要源于乐观更新的数据构建错误和mutate调用方式不当:
- 手动构建
newObjectUpdated时,可能未正确深拷贝原数据或精准定位目标歌曲,导致缓存数据结构异常 - 两次调用
mutate都传入相同的乐观数据,第二次本该用服务器返回的真实数据更新,却重复使用乐观数据,引发UI闪烁
修复方案
1. 用函数式更新构建乐观数据
利用SWR mutate的函数式参数,基于当前缓存的最新数据生成乐观更新后的结构,避免手动构建数据出错:
const { mutate } = useSWRConfig(); const isSongPlayed = async (song: Song, bool: boolean) => { // 执行乐观更新:基于当前缓存数据修改 const optimisticUpdate = mutate(`/api/events/${id}`, (currentEvent) => { if (!currentEvent) return currentEvent; // 深拷贝原数据,避免修改缓存原对象 const updatedEvent = JSON.parse(JSON.stringify(currentEvent)); // 定位到目标歌曲并更新状态 updatedEvent.moments.forEach(moment => { const targetSong = moment.songs.find(s => s._id === song._id); if (targetSong) { targetSong.played = bool; } }); return updatedEvent; }, { optimisticData: true, rollbackOnError: true, // 请求失败自动回滚到更新前状态 populateCache: true }); try { const response = await fetch(`/api/songs/${song._id}`, { method: "PUT", headers: { Accept: "application/json", "Content-Type": "application/json", }, body: JSON.stringify({ ...song, played: bool }), }); const playedSong = await response.json(); if (!playedSong.success || !playedSong.data) { setLoading(true); optimisticUpdate?.rollback(); // 手动触发回滚 return; } // 请求成功后,让SWR重新验证数据,确保缓存与服务器一致 mutate(`/api/events/${id}`); } catch (error) { console.error(error); // 可在此添加用户可见的错误提示,比如Toast } }; const onSetAsPlayed = (song: Song) => { isSongPlayed(song, !song.played); // 简化状态切换逻辑 };
2. 关键优化点说明
- 函数式更新:通过
(currentEvent) => {...}直接基于当前缓存生成新数据,避免手动构建newObjectUpdated时的结构错误 - 自动回滚:设置
rollbackOnError: true,请求失败时SWR会自动将缓存恢复到更新前状态,避免UI停留在错误状态 - 深拷贝数据:用
JSON.parse(JSON.stringify(currentEvent))深拷贝原数据,防止直接修改缓存对象导致的状态混乱(若数据包含函数或特殊类型,可改用lodash的cloneDeep) - 请求成功后重新验证:最后调用
mutate(/api/events/${id})让SWR从服务器拉取最新数据,确保缓存与后端完全一致
3. 额外优化建议
- 不要直接修改传入的
song对象:onSetAsPlayed中通过参数传递新状态,避免污染原数据 - 加载状态优化:改用SWR自带的
isLoading状态,或给mutate添加onLoading回调,减少手动维护setLoading的成本 - 错误反馈:在catch块中添加用户可见的错误提示,提升体验
内容的提问来源于stack exchange,提问作者Tiago Pereira
相关产品推荐
相关产品推荐

