如何使用SWR hook实现覆盖所有CRUD场景的API请求?
SWR 适配全量CRUD操作实现方案
SWR的核心定位是处理GET类数据请求的缓存、重验证逻辑,POST/PUT/DELETE这类写操作不需要单独封装绑定缓存key的Hook,只需要在请求完成后调用mutate方法更新对应GET接口的本地缓存,就能完成原有CRUD逻辑的适配,同时自动对齐加载、错误状态处理。
第一步:优化原有GET请求Hook,补全Token鉴权逻辑
你之前写的Hook缺少鉴权头注入、错误状态抛出的逻辑,先调整成通用可复用版本:
import useSWR, { useSWRConfig } from 'swr'; // 统一请求封装,自动注入Token鉴权头 const fetcher = async (url) => { const res = await fetch(url, { headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}`, // 替换成你项目实际的Token存储取值逻辑 'Content-Type': 'application/json' } }); // 主动抛出HTTP错误,才能被SWR的error字段捕获 if (!res.ok) throw new Error(`请求错误:${res.status}`); return res.json(); } // 通用GET请求Hook export default function useGetData(apiKey) { const { data, mutate, error, isLoading } = useSWR(apiKey, fetcher); return { loading: isLoading, data, error, mutate } }
第二步:POST/PUT/DELETE写操作实现逻辑
写操作直接在业务事件(表单提交、按钮点击等)回调中发起即可,核心是配合mutate做缓存更新,推荐用乐观更新模式提升交互体验:
- 发请求前直接修改本地缓存,给用户即时反馈,不需要等接口返回
- 接口请求成功后,触发对应GET接口的重验证,保证本地缓存和服务端数据一致
- 接口请求失败时,回滚本地缓存到修改前的状态,同时抛出错误提示
以用户信息模块的CRUD场景为例,组件内的实现代码如下:
function UserSettingPage() { // 拿到全局mutate实例,可更新任意key对应的缓存 const { mutate } = useSWRConfig(); // 调用封装好的GET Hook拉取用户信息 const { loading, data: userInfo, error } = useGetData('/api/user/info'); // POST:新增收货地址 const addAddress = async (addressForm) => { // 先更新本地缓存,不触发重验证 mutate('/api/user/info', { ...userInfo, addressList: [...userInfo.addressList, addressForm] }, false); try { const res = await fetch('/api/user/address', { method: 'POST', headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}`, 'Content-Type': 'application/json' }, body: JSON.stringify(addressForm) }); if (!res.ok) throw new Error('新增地址失败'); // 请求成功,重验证拉取最新服务端数据 mutate('/api/user/info'); } catch (err) { // 请求失败,回滚缓存 mutate('/api/user/info', userInfo, false); console.error(err); } } // PUT:修改用户昵称 const updateNickname = async (newNickname) => { const oldData = structuredClone(userInfo); mutate('/api/user/info', {...userInfo, nickname: newNickname}, false); try { await fetch('/api/user/info', { method: 'PUT', headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}`, 'Content-Type': 'application/json' }, body: JSON.stringify({nickname: newNickname}) }); mutate('/api/user/info'); } catch (err) { mutate('/api/user/info', oldData, false); } } // DELETE:删除收货地址 const deleteAddress = async (addressId) => { const oldData = structuredClone(userInfo); mutate('/api/user/info', { ...userInfo, addressList: userInfo.addressList.filter(item => item.id !== addressId) }, false); try { await fetch(`/api/user/address/${addressId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}`, } }); mutate('/api/user/info'); } catch (err) { mutate('/api/user/info', oldData, false); } } if (loading) return <div>加载中...</div> if (error) return <div>数据加载失败</div> return ( <div> {/* 页面渲染逻辑,将上面三个方法绑定到对应按钮、表单提交事件即可 */} <p>当前昵称:{userInfo.nickname}</p> </div> ) }
适配注意点
- 如果项目中写操作较多,可以把带Token注入、错误处理的fetch逻辑抽成通用request工具函数,避免每次重复写headers配置
- 乐观更新是可选优化,如果业务对数据一致性要求极高,可以等写请求返回成功后再调用mutate刷新缓存
- 如果写操作会影响多个接口的返回数据(比如删除列表项后,列表接口、详情接口的缓存都需要更新),调用mutate时传入所有关联的apiKey即可
- 如果需要全局处理写操作的加载状态,可以自己封装一个通用的useMutation Hook,内部维护loading、error状态,本质逻辑和上面的示例一致
内容的提问来源于stack exchange,提问作者mansoureh.hedayat
相关产品推荐
相关产品推荐

