SWR使用总结

5,945 阅读4分钟

SWR是什么

用于数据请求的 React Hooks 库。

SWR = stale-while-revalidate
一种由 HTTP RFC 5861 推广的 HTTP 缓存失效策略。这种策略首先从缓存中返回数据(过期的),同时发送 fetch 请求(重新验证),最后得到最新数据。

特性

  • 极速、轻量、可重用的 数据请求
  • 内置 缓存 和重复请求去除
  • 实时 体验
  • 支持TypeScript、React Native

场景

  • 快速页面导航
  • 间隔轮询
  • 数据依赖
  • 聚焦时重新验证
  • 网络恢复时重新验证
  • 本地缓存更新 (Optimistic UI)
  • 智能错误重试
  • 分页和滚动位置恢复
  • React Suspense

语法

const { data, error } = useSWR('/api/user', fetcher)

参数:

  • key是唯一标识符(string / function / array / null)
    • 如果传入的是字符串,那么这个字符串就是序列化后的key
    • 如果传入的是函数,那么执行这个函数,返回的结果就是序列化后的key
    • 如果传入的是数组,那么通过 hash方法(类似hash算法,数组的值序列化后唯一)序列化后的值就是key
  • fetcher(可选)是任何返回数据的异步函数
  • options(可选)

返回:

  • data
  • error
  • isValidating: 是否有请求或重新验证加载
  • mutate

示例

场景:打开一个页面,获取用户信息,导航条显示“欢迎xxx”,用户信息展示用户的头像、性别等信息

正常Hooks的思路:Page页面,useEffect获取用户信息,传参给NaviBar组件显示“欢迎xxx”,传参给UserInfo组件展示头像等信息

// 页面组件
function Page() {
  const [user, setUser] = useState(null)

  // 请求数据
  useEffect(() => {
    fetch("/get/user_info")
      .then((res) => res.json())
      .then((data) => setUser(data))
  }, [])

  // 全局加载状态
  if (!user) return null;

  return (
    <div>
      <NaviBar user={user} />
      <UserInfo user={user} />
    </div>
  )
}

// 导航条子组件
function NaviBar({ user }) {
  return (
    <div>
      {`欢迎${user.name}`}
    </div>
  )
}

//用户信息子组件
function UserInfo({ user }) {
  return <div>
      <h1>{`性别:${user.}`}</h1>
      <Avatar user={user}>
  </div>
}

//子组件的子组件
function Avatar({ user }) {
  return <img src={user.avatar} alt={user.name} />
}

这种写法的缺点是:

需要将所有的数据请求都保存在顶级组件中,并为树深处的每个组件添加 props。如果我们给页面添加更多的数据依赖,代码将变得更加难以维护。

SWR的写法:

// 页面组件(不请求数据)
function Page() {
  return (
    <div>
      <NaviBar />
      <CoUserInfontent />
    </div>
  );
}


// 导航条子组件(无需传参,内部获取)
function NaviBar() {
  const { user, isLoading } = useUser();
  return (
    <div>
      {`欢迎${user.name}`}
    </div>
  )
}

//用户信息子组件 (无需传参,内部获取)
function UserInfo() {
  const { user, isLoading } = useUser();
  return <div>
      <h1>{`性别:${user.}`}</h1>
      <Avatar user={user}>
  </div>
}

//子组件的子组件 (无需传参,内部获取)
function Avatar({ user }) {
  const { user, isLoading } = useUser();
  return <img src={user.avatar} alt={user.name} />
}

//获取数据
const useUser = ({} = {}) => {
  const url = '/get/user_info';
  return useSWR(url, fetcher);
};

const fetcher = (url) =>{
    return fetch("/get/user_info")
      .then((res) => res.json())
      .then((data) => setUser(data))
};

数据绑定到需要该数据的组件上,所有组件都是相互独立的。所有的父组件都不需要关心关于数据或数据传递的任何信息。它们只是渲染。现在代码更简单,更易于维护了。

只会有1个请求发送到API,因为它们使用相同的 SWR key,因此请求会被自动 去除重复、缓存 和 共享。

SWR 默认 深度比较 数据更改。如果 data 值没有改变,则不会触发重新渲染。

源码解读

  1. 合并参数config,优先级defaultConfig < Context.config < 自定义Config
  2. concurrent_promises( [kən'kʌrənt] 并发的 )变量用来保存所有需要并行的请求操作 判断是否已经存在,存在则读缓存或者等待结果,不存在则发起请求
  3. 请求过程中会判断是否超时,请求成功则cache.set保存缓存,然后broadcastState(key, newData, newState.error, false)用于让所有hooks都更新数据,失败后还会处理重试机制
  4. mutate 请求、重置时间戳
  5. 用的同一处缓存,当缓存更新的时候,会触发内部 notify 接口通知到所有订阅了相关更新的处理函数,从而各个使用方可以监听到数据的变化,SWR对外暴露的状态是以响应式的方式进行处理,setState触发组件的自动更新。

使用时注意事项

1、请求参数的唯一性

2、缓存数据在被所有页面共享,要注意不能修改

  • 从A页面获取了列表数据list, 但是A页面中只展示一个,list.slice(0, 1)
  • 从B页面也获取list数据,此时获取到的list长度也只有1

3、不能通过SWR获取常量或者变量

4、不能中断请求

5、 key 对应的响应结果没有被删除,需要手动清理缓存,避免内存泄露