You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 04:48:17