简介

SWR(Stale-While-Revalidate)是由 Vercel 团队开发的轻量级 React Hooks 库,专注于数据获取的缓存与同步。其核心思想源于 HTTP 缓存策略,先返回缓存数据(stale),同时后台请求最新数据(revalidate),最终更新 UI。这一策略确保了页面的快速响应和数据的实时性,特别适合构建高性能的现代 Web 应用。

SWR 的主要优势包括:

  • 自动缓存与请求去重:相同 key 的请求只会发送一次,数据自动缓存
  • 实时更新:支持聚焦重新验证、网络恢复自动刷新、定时轮询
  • 轻量级:约 4.2KB gzipped,无冗余依赖
  • 灵活的错误处理:内置错误重试机制
  • TypeScript 支持:完善的类型定义,提升开发体验

安装与基本使用

安装

使用 npm 或 yarn 安装 SWR:

npm install swr
# 或
yarn add swr

基础用法

SWR 的核心是 useSWR Hook,接收 key、fetcher 函数和配置选项,返回数据、错误状态和加载状态:

import useSWR from 'swr'

// 定义数据获取函数
const fetcher = url => fetch(url).then(res => res.json())

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

  if (error) return <div>Failed to load</div>
  if (isLoading) return <div>Loading...</div>
  return <div>Hello {data.name}!</div>
}

代码说明

  • key:请求的唯一标识符(通常是 API URL)
  • fetcher:返回 Promise 的异步函数,处理实际数据获取
  • 返回值:data(获取的数据)、error(错误信息)、isLoading(加载状态)

核心功能详解

缓存机制与请求去重

SWR 会自动缓存请求结果,相同 key 的请求会共享缓存数据,避免重复请求:

// 组件 A
const { data } = useSWR('/api/user', fetcher)

// 组件 B(同一页面)
const { data } = useSWR('/api/user', fetcher)
// 不会发送新请求,直接使用缓存数据

自动重新验证

SWR 提供多种触发数据更新的方式:

const { data } = useSWR('/api/todos', fetcher, {
  revalidateOnFocus: true,    // 窗口聚焦时重新验证(默认 true)
  revalidateOnReconnect: true, // 网络恢复时重新验证(默认 true)
  refreshInterval: 10000,      // 定时轮询(10秒一次,默认 0 关闭)
  focusThrottleInterval: 60000 // 聚焦节流间隔(60秒内最多一次)
})

加载与错误状态管理

SWR 提供独立的状态标识,便于精细化 UI 控制:

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

// 首次加载
if (isLoading) return <Spinner />

// 加载失败
if (error) return <ErrorUI message={error.message} />

// 后台重新验证中
if (isValidating) return <div>Updating...</div>

// 渲染数据
return <DataUI data={data} />

高级用法

数据变更与乐观 UI

SWR 2.0+ 引入 useSWRMutation 处理数据变更,支持乐观更新:

import useSWRMutation from 'swr/mutation'

// 定义更新函数
async function updateTodo(url, { arg }) {
  const res = await fetch(url, {
    method: 'POST',
    body: JSON.stringify(arg)
  })
  return res.json()
}

function TodoForm() {
  const { trigger, isMutating } = useSWRMutation('/api/todos', updateTodo)

  return (
    <button
      disabled={isMutating}
      onClick={() => trigger({ title: 'New Todo' })}
    >
      {isMutating ? 'Saving...' : 'Add Todo'}
    </button>
  )
}

乐观 UI 更新示例:

const { data, mutate } = useSWR('/api/todos', fetcher)

const addTodo = async (newTodo) => {
  // 乐观更新 UI
  mutate(
    async () => {
      const res = await fetch('/api/todos', {
        method: 'POST',
        body: JSON.stringify(newTodo)
      })
      return res.json()
    },
    {
      optimisticData: [...data, newTodo], // 预更新缓存
      populateCache: true,                // 使用响应数据更新缓存
      revalidate: false                   // 不需要额外验证
    }
  )
}

分页与无限滚动

使用 useSWRInfinite 实现分页加载:

import useSWRInfinite from 'swr/infinite'

function Products() {
  const getKey = (pageIndex, previousPageData) => {
    if (previousPageData && !previousPageData.next) return null
    return `/api/products?page=${pageIndex + 1}`
  }

  const { data, error, isLoading, size, setSize } = useSWRInfinite(getKey, fetcher)

  const isLoadingMore = isLoading || (data && typeof data[size - 1] === 'undefined')
  const allProducts = data ? [].concat(...data) : []

  return (
    <div>
      {allProducts.map(product => (
        <Product key={product.id} product={product} />
      ))}
      <button
        disabled={isLoadingMore || !data?.[size - 1]?.next}
        onClick={() => setSize(size + 1)}
      >
        {isLoadingMore ? 'Loading...' : 'Load More'}
      </button>
    </div>
  )
}

依赖请求与条件请求

SWR 支持基于其他数据的依赖请求和条件请求:

// 依赖请求
function UserProjects() {
  const { data: user } = useSWR('/api/user', fetcher)
  // 仅当 user 加载完成后才请求 projects
  const { data: projects } = useSWR(() => user ? `/api/projects?uid=${user.id}` : null, fetcher)

  if (!projects) return 'Loading...'
  return `You have ${projects.length} projects`
}

// 条件请求
function Dashboard({ shouldFetch }) {
  // 当 shouldFetch 为 false 时不发送请求
  const { data } = useSWR(shouldFetch ? '/api/data' : null, fetcher)
  return <div>{data?.content}</div>
}

全局配置

通过 SWRConfig 提供全局配置,避免重复设置:

import { SWRConfig } from 'swr'

function App() {
  return (
    <SWRConfig
      value={{
        fetcher: url => fetch(url).then(res => res.json()),
        refreshInterval: 30000,
        revalidateOnFocus: false
      }}
    >
      <Dashboard />
    </SWRConfig>
  )
}

// 组件中无需重复传递 fetcher
function Dashboard() {
  const { data } = useSWR('/api/data') // 自动使用全局 fetcher
  return <div>{data?.content}</div>
}

性能优化与最佳实践

减少不必要的渲染

SWR 的 dataerrorisValidating 状态独立更新,同时使用会导致多次渲染。建议根据实际需求选择状态组合:

// 推荐:根据数据状态判断
if (!data) return <Loading />
if (error) return <Error />

// 不推荐:同时使用三个状态导致多次渲染
if (isLoading) return <Loading />
if (error) return <Error />
if (data) return <Content data={data} />

处理静态数据

对于不常变化的静态数据,使用 useSWRImmutable 禁用自动重新验证:

import useSWRImmutable from 'swr/immutable'

// 等同于 useSWR(key, fetcher, { revalidateIfStale: false, revalidateOnFocus: false, revalidateOnReconnect: false })
const { data } = useSWRImmutable('/api/static-data', fetcher)

自定义缓存键策略

使用数组或对象作为 key,处理带参数的请求:

// 数组 key(自动序列化)
const { data } = useSWR(['/api/user', userId, { details: true }], fetcher)

// 对象 key(需要自定义序列化)
const { data } = useSWR(
  { url: '/api/user', id: userId, details: true },
  ({ url, id }) => fetch(`${url}/${id}`).then(res => res.json())
)

框架集成

Next.js 14 集成

在 Next.js 14 App Router 中使用 SWR:

// app/page.js
'use client'

import useSWR from 'swr'

const fetcher = url => fetch(url).then(res => res.json())

export default function Home() {
  const { data, error } = useSWR('/api/featured-posts', fetcher)

  if (error) return <div>Failed to load</div>
  if (!data) return <div>Loading...</div>

  return (
    <main>
      <h1>Featured Posts</h1>
      <ul>
        {data.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </main>
  )
}

GraphQL 集成

结合 graphql-codegen-plugin-typescript-swr 自动生成 SWR hooks:

# schema.graphql
type User {
  id: ID!
  name: String!
  email: String!
}

type Query {
  user(id: ID!): User
}

生成 SWR hooks 后使用:

import { useUserQuery } from '../generated/graphql'

function UserProfile({ userId }) {
  const { data, error } = useUserQuery({ variables: { id: userId } })

  if (error) return <div>Error loading user</div>
  if (!data?.user) return <div>Loading...</div>

  return <div>Name: {data.user.name}</div>
}

与 React Query 的对比

特性 SWR React Query
体积 ~4.2KB (gzipped) ~12KB (gzipped)
核心理念 专注数据获取与缓存 完整的服务器状态管理
mutations 需要手动实现(v2+ 提供 useSWRMutation) 内置 useMutation
缓存控制 简单灵活 更精细的配置选项
学习曲线 较低 中等
适用场景 中小型应用、注重轻量 大型复杂应用、需要高级功能

选择建议

  • 小型项目或需要轻量级解决方案时选择 SWR
  • 大型应用或需要复杂状态管理时选择 React Query

实际应用案例

实时聊天应用

结合 SignalR 和 SWR 实现实时消息更新:

// hooks/useChat.js
import useSWR from 'swr'
import { HubConnectionBuilder } from '@microsoft/signalr'

export function useChat() {
  const { data: messages, mutate } = useSWR('/api/messages', fetcher, {
    revalidateOnFocus: false
  })

  // 建立 SignalR 连接
  useEffect(() => {
    const connection = new HubConnectionBuilder()
      .withUrl('/chatHub')
      .build()

    connection.on('NewMessage', (message) => {
      // 收到新消息时更新缓存
      mutate(prev => [...prev, message], false)
    })

    connection.start()
    return () => connection.stop()
  }, [mutate])

  return { messages, mutate }
}

代码片段管理工具

使用 SWR 管理用户代码片段:

// hooks/useSnippets.js
import useSWRInfinite from 'swr/infinite'

export function useSnippets() {
  const getKey = (pageIndex) => `/api/snippets?page=${pageIndex + 1}`
  
  const { data, size, setSize, mutate } = useSWRInfinite(getKey, fetcher)

  const snippets = data ? [].concat(...data) : []
  const isLoading = !data && size === 1
  const isLoadingMore = isLoading || (data && typeof data[size - 1] === 'undefined')

  const addSnippet = async (snippet) => {
    await fetch('/api/snippets', {
      method: 'POST',
      body: JSON.stringify(snippet)
    })
    // 添加成功后重新验证
    mutate()
  }

  return {
    snippets,
    isLoading,
    isLoadingMore,
    loadMore: () => setSize(size + 1),
    addSnippet
  }
}

总结与资源

SWR 作为轻量级数据获取库,通过 Stale-While-Revalidate 策略平衡了性能与实时性,简化了 React 应用中的数据管理逻辑。其核心优势在于自动缓存、智能重新验证和简洁的 API 设计,特别适合中小型应用和注重开发体验的项目。

官方资源

通过合理使用 SWR,开发者可以构建出响应更快、用户体验更优的现代 React 应用。

更多推荐