tanstack中的react-query和SWR使用及对比

TanStack Query vs SWR 深度对比与最佳实践

目录

  • 概述
  • 核心概念对比
  • TanStack Query 详解
  • TanStack Query 缓存机制
  • SWR 详解
  • SWR 缓存机制
  • 性能对比分析
  • 使用场景选择
  • 最佳实践
  • 迁移指南

概述

在现代 React 应用开发中,数据获取和状态管理是核心需求。TanStack Query(原 React Query)和 SWR 是两个最流行的数据获取库,它们都提供了缓存、重新验证、错误处理等功能,但在设计理念和使用方式上存在差异。

核心价值对比

特性 TanStack Query SWR
设计理念 功能丰富的数据管理平台 轻量级数据获取库
学习曲线 中等到复杂 简单到中等
包大小 较大 (~39KB) 较小 (~24KB)
生态系统 丰富的插件和工具 简洁的核心功能
TypeScript 优秀的类型支持 良好的类型支持

核心概念对比

数据获取模式

TanStack Query
SWR
用户触发
选择数据获取库
useQuery Hook
useSWR Hook
Query Key管理
Query Function执行
缓存策略
SWR Key管理
Fetcher函数执行
缓存与重新验证
返回查询状态

TanStack Query 详解

基础使用

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// 基础查询示例
function UserProfile({ userId }) {
  const { 
    data: user, 
    isLoading, 
    error, 
    isError,
    refetch 
  } = useQuery({
    queryKey: ['user', userId],
    queryFn: async () => {
      const response = await fetch(`/api/users/${userId}`);
      if (!response.ok) {
        throw new Error('获取用户信息失败');
      }
      return response.json();
    },
    // 缓存时间
    staleTime: 5 * 60 * 1000, // 5分钟
    // 缓存垃圾回收时间
    cacheTime: 10 * 60 * 1000, // 10分钟
    // 重试配置
    retry: (failureCount, error) => {
      if (error.status === 404) return false;
      return failureCount < 3;
    },
    // 重试延迟
    retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
  });

  if (isLoading) return <UserSkeleton />;
  if (isError) return <ErrorMessage error={error} onRetry={refetch} />;

  return (
    <div className="user-profile">
      <img src={user.avatar} alt={user.name} />
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

高级查询功能

// 并行查询
function Dashboard({ userId }) {
  const userQuery = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId)
  });

  const postsQuery = useQuery({
    queryKey: ['posts', userId],
    queryFn: () => fetchUserPosts(userId),
    // 依赖查询 - 等用户数据加载完成
    enabled: !!userQuery.data
  });

  const statsQuery = useQuery({
    queryKey: ['stats', userId],
    queryFn: () => fetchUserStats(userId),
    enabled: !!userQuery.data
  });

  const queries = [userQuery, postsQuery, statsQuery];
  const isLoading = queries.some(query => query.isLoading);
  const hasError = queries.some(query => query.isError);

  if (isLoading) return <DashboardSkeleton />;
  if (hasError) return <ErrorBoundary />;

  return (
    <div className="dashboard">
      <UserInfo user={userQuery.data} />
      <PostsList posts={postsQuery.data} />
      <StatsWidget stats={statsQuery.data} />
    </div>
  );
}

// 无限查询
function PostsList() {
  const {
    data,
    error,
    fetchNextPage,
    hasNextPage,
    isFetching,
    isFetchingNextPage,
  } = useInfiniteQuery({
    queryKey: ['posts'],
    queryFn: async ({ pageParam = 0 }) => {
      const response = await fetch(`/api/posts?page=${pageParam}&limit=10`);
      return response.json();
    },
    getNextPageParam: (lastPage, pages) => {
      return lastPage.hasMore ? pages.length : undefined;
    },
  });

  const posts = data?.pages.flatMap(page => page.posts) ?? [];

  return (
    <div className="posts-list">
      {posts.map(post => (
        <PostCard key={post.id} post={post} />
      ))}
      
      {hasNextPage && (
        <button
          onClick={() => fetchNextPage()}
          disabled={isFetchingNextPage}
          className="load-more-btn"
        >
          {isFetchingNextPage ? '加载中...' : '加载更多'}
        </button>
      )}
      
      {isFetching && !isFetchingNextPage && <LoadingSpinner />}
    </div>
  );
}

变更操作

// 数据变更和乐观更新
function PostEditor({ postId, onSuccess }) {
  const queryClient = useQueryClient();
  
  const { data: post } = useQuery({
    queryKey: ['post', postId],
    queryFn: () => fetchPost(postId)
  });

  const updateMutation = useMutation({
    mutationFn: async (updatedPost) => {
      const response = await fetch(`/api/posts/${postId}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(updatedPost)
      });
      if (!response.ok) throw new Error('更新失败');
      return response.json();
    },
    // 乐观更新
    onMutate: async (newPost) => {
      // 取消正在进行的查询
      await queryClient.cancelQueries({ queryKey: ['post', postId] });
      
      // 保存之前的数据用于回滚
      const previousPost = queryClient.getQueryData(['post', postId]);
      
      // 乐观更新
      queryClient.setQueryData(['post', postId], {
        ...previousPost,
        ...newPost,
        updatedAt: new Date().toISOString()
      });
      
      return { previousPost };
    },
    // 成功时更新相关查询
    onSuccess: (data) => {
      queryClient.setQueryData(['post', postId], data);
      // 更新列表数据
      queryClient.invalidateQueries({ queryKey: ['posts'] });
      onSuccess?.(data);
    },
    // 错误时回滚
    onError: (err, newPost, context) => {
      if (context?.previousPost) {
        queryClient.setQueryData(['post', postId], context.previousPost);
      }
    },
    // 无论成功失败都重新获取数据
    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ['post', postId] });
    },
  });

  const handleSubmit = (formData) => {
    updateMutation.mutate(formData);
  };

  if (!post) return <PostSkeleton />;

  return (
    <PostForm
      initialData={post}
      onSubmit={handleSubmit}
      isSubmitting={updateMutation.isPending}
      error={updateMutation.error}
    />
  );
}

高级配置和中间件

// 全局配置
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      // 全局查询配置
      staleTime: 5 * 60 * 1000,
      cacheTime: 10 * 60 * 1000,
      retry: (failureCount, error) => {
        // 全局重试逻辑
        if (error.status >= 400 && error.status < 500) {
          return false; // 客户端错误不重试
        }
        return failureCount < 3;
      },
      // 全局错误处理
      onError: (error) => {
        console.error('Query Error:', error);
        if (error.status === 401) {
          // 处理认证错误
          window.location.href = '/login';
        }
      },
    },
    mutations: {
      // 全局变更配置
      onError: (error) => {
        console.error('Mutation Error:', error);
        // 显示错误提示
        toast.error(error.message);
      },
    },
  },
});

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Router>
        <Routes>
          {/* 路由配置 */}
        </Routes>
      </Router>
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

TanStack Query 缓存机制

TanStack Query 提供了强大而灵活的缓存系统,能够智能地管理数据的生命周期,提供优秀的用户体验。

能否判断是否是否新鲜?是否过期?

TanStack Query(React Query)之所以能判断缓存是否“新鲜”或“过期”,是因为它对每一条缓存数据都维护了完整的生命周期元信息(metadata),并且采用了 时间驱动的缓存策略

TanStack Query 会记录每条缓存的“最后更新时间”,并对照配置项判断数据是否还“新鲜”或“过期”。

  • 每条缓存都有精确的更新时间(dataUpdatedAt
  • 配合用户设定的 staleTimecacheTime
  • 系统自动判断缓存是否 stale 或 expired,决定是否重新 fetch 或垃圾回收

生命周期元信息

生命周期元信息(metadata),指的是它为每一个 query 自动维护的一组状态与时间戳,这些信息帮助它判断:

  • 缓存是否**新鲜(fresh)*还是*陈旧(stale)
  • 缓存是否应该重新请求(refetch)
  • 缓存是否可以被自动清除(garbage collected)
  • Query 当前状态是 loading、success、error,还是 idle
  • 是否在被使用中(active)
生命周期元信息包含哪些?

每一个 Query 实例内部会维护类似以下结构:

{
  queryKey: ['todos'],
  state: {
    data,                    // 当前缓存的数据
    dataUpdatedAt,           // 上次 fetch 成功的时间戳(用于 stale 判断)
    error,                   // 最近一次请求的错误
    errorUpdatedAt,          // 错误时间
    fetchStatus,             // 'idle' | 'fetching' | 'paused'
    status,                  // 'loading' | 'success' | 'error'
    isInvalidated,           // 是否被标记为“无效”——触发 refetch
    isPaused,                // 当前是否暂停请求
    fetchMeta                // 请求的一些附加信息,如 retry 次数等
  },
  observers: [...],          // 哪些组件正在使用它(用于 active 判断)
  gcTimeout,                 // GC 回收的定时器
  options: { staleTime, cacheTime, ... }
}

关键字段说明
字段名 含义
dataUpdatedAt 上次请求成功的时间戳,用于判断是否 stale
status 当前 query 状态:loading / success / error
fetchStatus 是否正在请求中:fetching / idle
isInvalidated 被标记为“无效”,会触发下次自动刷新
observers 有多少组件在“使用”这个 query,决定是否需要 GC
gcTimeout 缓存未使用后触发的垃圾回收计时器

生命周期流程简图
flowchart TD
  A[初次 useQuery 请求] --> B[fetch 成功]
  B --> C[记录 dataUpdatedAt 时间戳]
  C --> D{超过 staleTime?}
  D -- 否 --> E[认为 fresh,不触发 refetch]
  D -- 是 --> F[标记为 stale,下次激活会 refetch]
  F --> G{有组件在用?}
  G -- 否 --> H[启动 GC 计时器,等待 cacheTime]
  H --> I[清除缓存数据]

应用场景:为什么需要这些 metadata?
自动刷新数据的时机判断
if (Date.now() - dataUpdatedAt > staleTime) {
  // 数据是 stale 的,触发 refetch
}
判断是否有组件在用(决定是否 GC)
if (query.observers.length === 0) {
  // 启动 GC 计时器,准备删除缓存
}
控制 UI 状态反馈
if (status === 'loading') showSpinner()
if (status === 'error') showError()

想看 metadata?React Query Devtools!

如果你使用 React Query Devtools,可以看到每个 query 的元信息:

  • Data
  • isStale
  • UpdatedAt 时间
  • 状态字段
  • 是否 active
  • 是否正在 fetching

示例图:

 Query ['todos']
- Status: success
- isStale: true
- Data updated at: 12:03:12 PM
- Active observers: 1
- Fetching: false

TanStack Query 的 生命周期元信息(metadata) 是一整套用于管理缓存生命周期的状态和时间戳数据,它支撑了 query 的核心功能,包括:

  • 是否需要重新请求?
  • 数据是否还新鲜?
  • 缓存是否可以清除?
  • UI 如何正确反馈加载、错误状态?

这些 metadata 让 TanStack Query 成为“智能数据管理库”而不仅仅是“数据请求库”。

缓存生命周期

新鲜
过期
查询触发
缓存中有数据?
数据是否新鲜?
发起网络请求
返回缓存数据
返回缓存数据 + 后台更新
获取新数据
更新缓存
通知组件更新
还有组件使用?
保持活跃
开始垃圾回收计时
超时后清除缓存

缓存状态管理

// 缓存状态示例
function CacheStatusDemo() {
  const queryClient = useQueryClient();
  
  const { data, status, fetchStatus, dataUpdatedAt } = useQuery({
    queryKey: ['posts'],
    queryFn: fetchPosts,
    staleTime: 5 * 60 * 1000, // 5分钟内认为数据新鲜
    cacheTime: 30 * 60 * 1000, // 30分钟后垃圾回收
  });

  // 查询状态详解
  const queryState = {
    // status: 查询的状态
    // 'pending' - 首次加载中
    // 'error' - 查询失败
    // 'success' - 查询成功
    status,
    
    // fetchStatus: 网络请求状态
    // 'idle' - 没有网络请求
    // 'fetching' - 正在请求
    fetchStatus,
    
    // 数据相关
    hasData: !!data,
    dataUpdatedAt: new Date(dataUpdatedAt).toLocaleString(),
  };

  return (
    <div className="cache-demo">
      <h3>缓存状态监控</h3>
      <pre>{JSON.stringify(queryState, null, 2)}</pre>
      
      <div className="actions">
        <button onClick={() => queryClient.invalidateQueries({ queryKey: ['posts'] })}>
          使缓存失效
        </button>
        <button onClick={() => queryClient.removeQueries({ queryKey: ['posts'] })}>
          清除缓存
        </button>
        <button onClick={() => queryClient.resetQueries({ queryKey: ['posts'] })}>
          重置查询
        </button>
      </div>
    </div>
  );
}

缓存配置详解

// 详细的缓存配置
const cacheConfig = {
  // 数据新鲜度配置
  staleTime: 5 * 60 * 1000, // 5分钟内数据被认为是新鲜的
  
  // 缓存时间配置
  cacheTime: 30 * 60 * 1000, // 数据在内存中保存30分钟
  
  // 重新获取策略
  refetchOnMount: true, // 组件挂载时重新获取
  refetchOnWindowFocus: true, // 窗口获得焦点时重新获取
  refetchOnReconnect: true, // 网络重连时重新获取
  
  // 重试配置
  retry: (failureCount, error) => {
    if (error.status === 404) return false;
    return failureCount < 3;
  },
  retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
};

// 应用缓存配置
function PostsWithCache() {
  const { data: posts, isStale, isFetching } = useQuery({
    queryKey: ['posts'],
    queryFn: fetchPosts,
    ...cacheConfig,
  });

  return (
    <div className="posts-container">
      <div className="cache-indicators">
        {isStale && <span className="stale-badge">数据已过期</span>}
        {isFetching && <span className="fetching-badge">更新中...</span>}
      </div>
      <PostsList posts={posts} />
    </div>
  );
}

缓存更新策略

// 手动缓存更新
function CacheUpdateStrategies() {
  const queryClient = useQueryClient();
  
  // 策略1: 直接设置缓存数据
  const setUserData = (userId, userData) => {
    queryClient.setQueryData(['user', userId], userData);
  };
  
  // 策略2: 函数式更新
  const updateUserLikes = (userId) => {
    queryClient.setQueryData(['user', userId], (oldData) => {
      if (!oldData) return oldData;
      return {
        ...oldData,
        likes: oldData.likes + 1,
        updatedAt: new Date().toISOString(),
      };
    });
  };
  
  // 策略3: 批量更新相关缓存
  const updateUserAndPosts = async (userId, newUserData) => {
    // 更新用户数据
    queryClient.setQueryData(['user', userId], newUserData);
    
    // 更新相关的文章列表缓存
    queryClient.setQueryData(['posts', 'user', userId], (oldPosts) => {
      if (!oldPosts) return oldPosts;
      return oldPosts.map(post => ({
        ...post,
        author: newUserData,
      }));
    });
    
    // 使相关查询失效
    queryClient.invalidateQueries({ queryKey: ['posts', 'feed'] });
  };
  
  // 策略4: 条件性缓存更新
  const smartCacheUpdate = (data) => {
    queryClient.setQueriesData(
      { queryKey: ['posts'] },
      (oldData) => {
        // 只更新包含特定条件的缓存
        if (oldData?.meta?.category === data.category) {
          return {
            ...oldData,
            posts: [...oldData.posts, data],
          };
        }
        return oldData;
      }
    );
  };
  
  return {
    setUserData,
    updateUserLikes,
    updateUserAndPosts,
    smartCacheUpdate,
  };
}

缓存预取和预填充

// 缓存预取策略
function CachePrefetching() {
  const queryClient = useQueryClient();
  
  // 预取下一页数据
  const prefetchNextPage = async (currentPage) => {
    const nextPage = currentPage + 1;
    
    // 预取下一页,但不会触发加载状态
    await queryClient.prefetchQuery({
      queryKey: ['posts', 'page', nextPage],
      queryFn: () => fetchPosts({ page: nextPage }),
      staleTime: 5 * 60 * 1000,
    });
  };
  
  // 鼠标悬停时预取详情
  const prefetchPostDetail = async (postId) => {
    await queryClient.prefetchQuery({
      queryKey: ['post', postId],
      queryFn: () => fetchPost(postId),
      staleTime: 10 * 60 * 1000,
    });
  };
  
  // 预填充相关数据
  const prefillUserData = (users) => {
    users.forEach(user => {
      queryClient.setQueryData(['user', user.id], user);
    });
  };
  
  return (
    <div className="posts-list">
      {posts.map((post, index) => (
        <div
          key={post.id}
          onMouseEnter={() => prefetchPostDetail(post.id)}
          className="post-item"
        >
          <PostCard post={post} />
          {index === posts.length - 5 && prefetchNextPage(currentPage)}
        </div>
      ))}
    </div>
  );
}

缓存持久化

import { persistQueryClient } from '@tanstack/react-query-persist-client-core';
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';

// 配置持久化存储
const persister = createSyncStoragePersister({
  storage: window.localStorage,
  key: 'REACT_QUERY_OFFLINE_CACHE',
  serialize: JSON.stringify,
  deserialize: JSON.parse,
});

// 创建支持持久化的 QueryClient
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      cacheTime: 1000 * 60 * 60 * 24, // 24小时
      staleTime: 1000 * 60 * 5, // 5分钟
    },
  },
});

// 初始化持久化
persistQueryClient({
  queryClient,
  persister,
  maxAge: 1000 * 60 * 60 * 24, // 24小时
  buster: '1.0', // 版本控制
});

// 选择性持久化
function SelectivePersistence() {
  const { data } = useQuery({
    queryKey: ['user-settings'],
    queryFn: fetchUserSettings,
    // 这个查询会被持久化
    meta: {
      persist: true,
    },
  });
  
  const { data: tempData } = useQuery({
    queryKey: ['temp-data'],
    queryFn: fetchTempData,
    // 这个查询不会被持久化
    meta: {
      persist: false,
    },
  });
  
  return <div>{/* 组件内容 */}</div>;
}

缓存调试和监控

// 缓存调试工具
function CacheDebugger() {
  const queryClient = useQueryClient();
  const queryCache = queryClient.getQueryCache();
  
  // 获取所有缓存状态
  const getAllCacheInfo = () => {
    const queries = queryCache.getAll();
    return queries.map(query => ({
      queryKey: query.queryKey,
      state: query.state.status,
      dataUpdatedAt: query.state.dataUpdatedAt,
      errorUpdatedAt: query.state.errorUpdatedAt,
      fetchStatus: query.state.fetchStatus,
      isStale: query.isStale(),
      observersCount: query.getObserversCount(),
    }));
  };
  
  // 缓存性能监控
  const monitorCachePerformance = () => {
    const observer = {
      onQueryAdded: (query) => {
        console.log('Query Added:', query.queryKey);
      },
      onQueryRemoved: (query) => {
        console.log('Query Removed:', query.queryKey);
      },
      onQueryUpdated: (query) => {
        console.log('Query Updated:', query.queryKey, {
          fetchStatus: query.state.fetchStatus,
          status: query.state.status,
        });
      },
    };
    
    // 订阅缓存事件
    const unsubscribe = queryCache.subscribe(observer);
    
    return unsubscribe;
  };
  
  const [cacheInfo, setCacheInfo] = useState([]);
  
  useEffect(() => {
    const interval = setInterval(() => {
      setCacheInfo(getAllCacheInfo());
    }, 1000);
    
    const unsubscribe = monitorCachePerformance();
    
    return () => {
      clearInterval(interval);
      unsubscribe();
    };
  }, []);
  
  return (
    <div className="cache-debugger">
      <h3>缓存监控面板</h3>
      <table>
        <thead>
          <tr>
            <th>Query Key</th>
            <th>状态</th>
            <th>最后更新</th>
            <th>观察者数量</th>
            <th>是否过期</th>
          </tr>
        </thead>
        <tbody>
          {cacheInfo.map((info, index) => (
            <tr key={index}>
              <td>{JSON.stringify(info.queryKey)}</td>
              <td>{info.state}</td>
              <td>{new Date(info.dataUpdatedAt).toLocaleTimeString()}</td>
              <td>{info.observersCount}</td>
              <td>{info.isStale ? '是' : '否'}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

SWR 详解

基础使用

import useSWR, { mutate } from 'swr';

// 全局 fetcher 函数
const fetcher = async (url) => {
  const response = await fetch(url);
  if (!response.ok) {
    const error = new Error('请求失败');
    error.status = response.status;
    throw error;
  }
  return response.json();
};

// 基础用法
function UserProfile({ userId }) {
  const { 
    data: user, 
    error, 
    isLoading,
    mutate: refreshUser 
  } = useSWR(`/api/users/${userId}`, fetcher, {
    // 重新验证策略
    revalidateOnFocus: true,
    revalidateOnReconnect: true,
    refreshInterval: 30000, // 30秒自动刷新
    // 错误重试
    errorRetryCount: 3,
    errorRetryInterval: 5000,
    // 缓存配置
    dedupingInterval: 2000,
  });

  if (error) return <ErrorMessage error={error} onRetry={refreshUser} />;
  if (isLoading) return <UserSkeleton />;

  return (
    <div className="user-profile">
      <img src={user.avatar} alt={user.name} />
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      <button onClick={() => refreshUser()}>
        刷新数据
      </button>
    </div>
  );
}

高级功能

// 条件查询和依赖查询
function Dashboard({ userId }) {
  // 用户基础信息
  const { data: user } = useSWR(
    userId ? `/api/users/${userId}` : null,
    fetcher
  );

  // 依赖查询 - 只有用户信息加载完成后才执行
  const { data: posts } = useSWR(
    user ? `/api/users/${userId}/posts` : null,
    fetcher
  );

  const { data: stats } = useSWR(
    user ? `/api/users/${userId}/stats` : null,
    fetcher
  );

  if (!user) return <DashboardSkeleton />;

  return (
    <div className="dashboard">
      <UserInfo user={user} />
      {posts && <PostsList posts={posts} />}
      {stats && <StatsWidget stats={stats} />}
    </div>
  );
}

// 分页数据处理
function PostsList() {
  const [page, setPage] = useState(1);
  const [allPosts, setAllPosts] = useState([]);

  const { data, error, isLoading } = useSWR(
    `/api/posts?page=${page}&limit=10`,
    fetcher,
    {
      onSuccess: (newData) => {
        if (page === 1) {
          setAllPosts(newData.posts);
        } else {
          setAllPosts(prev => [...prev, ...newData.posts]);
        }
      }
    }
  );

  const loadMore = () => {
    if (data?.hasMore) {
      setPage(prev => prev + 1);
    }
  };

  if (error) return <ErrorMessage error={error} />;

  return (
    <div className="posts-list">
      {allPosts.map(post => (
        <PostCard key={post.id} post={post} />
      ))}
      
      {data?.hasMore && (
        <button onClick={loadMore} disabled={isLoading}>
          {isLoading ? '加载中...' : '加载更多'}
        </button>
      )}
    </div>
  );
}

数据变更和乐观更新

“先更新界面,再请求服务器,失败再回滚。”

// 乐观更新实现
function PostEditor({ postId }) {
  const { data: post, mutate } = useSWR(`/api/posts/${postId}`, fetcher);

  const updatePost = async (updatedData) => {
    try {
      // 乐观更新本地数据
      const optimisticData = { ...post, ...updatedData };
      mutate(optimisticData, false); // false = 不重新验证

      // 发送请求到服务器
      const response = await fetch(`/api/posts/${postId}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(updatedData)
      });

      if (!response.ok) throw new Error('更新失败');

      const serverData = await response.json();
      
      // 用服务器数据更新缓存
      mutate(serverData);
      
      // 更新相关的缓存
      mutate('/api/posts'); // 刷新文章列表
      
    } catch (error) {
      // 错误时恢复原始数据
      mutate();
      throw error;
    }
  };

  const handleSubmit = async (formData) => {
    try {
      await updatePost(formData);
      toast.success('更新成功');
    } catch (error) {
      toast.error(error.message);
    }
  };

  if (!post) return <PostSkeleton />;

  return (
    <PostForm
      initialData={post}
      onSubmit={handleSubmit}
    />
  );
}

SWR 配置和中间件

import { SWRConfig } from 'swr';

// 全局配置
function App() {
  return (
    <SWRConfig
      value={{
        fetcher,
        // 全局配置
        revalidateOnFocus: false,
        revalidateOnReconnect: true,
        refreshInterval: 0,
        dedupingInterval: 2000,
        // 全局错误处理
        onError: (error, key) => {
          console.error(`SWR Error for ${key}:`, error);
          if (error.status === 401) {
            window.location.href = '/login';
          }
        },
        // 全局成功处理
        onSuccess: (data, key) => {
          console.log(`SWR Success for ${key}`);
        },
        // 错误重试配置
        errorRetryCount: 3,
        errorRetryInterval: 5000,
        // 加载超时
        loadingTimeout: 3000,
        onLoadingSlow: (key) => {
          console.warn(`Slow loading for ${key}`);
        },
      }}
    >
      <Router>
        <Routes>
          {/* 路由配置 */}
        </Routes>
      </Router>
    </SWRConfig>
  );
}

SWR 缓存机制

SWR 的缓存机制以"stale-while-revalidate"模式为核心,提供了简洁而高效的数据管理方案。

SWR 缓存策略概览

用户请求数据
缓存中有数据?
立即返回缓存数据
显示loading状态
后台重新验证数据
发起网络请求
数据有更新?
获取最新数据
更新缓存和UI
保持当前数据
更新缓存
更新UI状态

判断缓存是否新鲜?是否过期?

SWR 的设计理念围绕 数据“新鲜度”Stale-While-Revalidate 策略),它不是通过传统的“缓存过期时间”控制缓存,而是通过以下几种新鲜度控制机制来实现“数据始终保持接近最新”。

SWR 如何控制缓存“新鲜度”?

控制方式 是否过期缓存 说明
revalidateOnFocus ✅ 控制重新获取 页面聚焦时是否刷新数据(默认 true
revalidateOnReconnect ✅ 控制重新获取 网络重新连接时是否刷新数据(默认 true
refreshInterval ✅ 定时刷新 设置间隔时间,定时自动重新请求数据
dedupingInterval ⛔ 非缓存过期 在该时间内重复请求同一 key,会被忽略(防止频繁请求)
keepPreviousData ⛔ 保持旧缓存 用于分页/变化型 key 的场景,保持上一次数据
cacheProvider ⛔ 自定义缓存策略 若你想实现 TTL(过期控制),可以在自定义 provider 中处理

SWR 的缓存不是传统的 TTL 缓存

SWR 的缓存是 “永不过期,但可以随时更新”,只要不手动或自动触发更新,它一直存在于缓存中(内存)。

所以:

  • 它并不会在 X 秒后自动清除缓存
  • 而是提供机制,在适当时机刷新数据
    • 页面重新聚焦
    • 网络恢复
    • 设置 refreshInterval
    • 手动 mutate
想实现“缓存过期”怎么办?

需要手动实现类似 TTL(Time-To-Live)的“定时清除缓存”的能力,可以结合 cacheProvider 自定义:

SWR 本身不使用“缓存过期”来控制缓存,而是通过“数据新鲜策略”(聚焦、连接恢复、定时刷新、手动 mutate 等)来保持缓存的数据始终是“接近最新”的。

示例:带过期时间的 localStorage 缓存方案
ts


复制编辑
const EXPIRATION_MS = 5 * 60 * 1000 // 5分钟

const ttlCacheProvider = () => {
  const map = new Map()

  const raw = localStorage.getItem('swr-ttl-cache')
  if (raw) {
    const entries: [string, { data: any, timestamp: number }][] = JSON.parse(raw)
    for (const [key, value] of entries) {
      if (Date.now() - value.timestamp < EXPIRATION_MS) {
        map.set(key, value.data)
      }
    }
  }

  window.addEventListener('beforeunload', () => {
    const entries = Array.from(map.entries()).map(([key, data]) => [key, { data, timestamp: Date.now() }])
    localStorage.setItem('swr-ttl-cache', JSON.stringify(entries))
  })

  return map
}

缓存键管理

// SWR 缓存键策略
function SWRCacheKeyStrategies() {
  // 基础字符串键
  const { data: user } = useSWR('/api/user', fetcher);
  
  // 带参数的键
  const { data: posts } = useSWR(`/api/posts?page=${page}&limit=${limit}`, fetcher);
  
  // 数组形式的键(推荐)
  const { data: userPosts } = useSWR(['posts', 'user', userId], () => 
    fetcher(`/api/users/${userId}/posts`)
  );
  
  // 条件查询键
  const { data: profile } = useSWR(
    userId ? ['user', 'profile', userId] : null, // null 会暂停查询
    () => fetcher(`/api/users/${userId}/profile`)
  );
  
  // 复杂查询键
  const { data: searchResults } = useSWR(
    ['search', { query, filters, sort }],
    ([, params]) => fetcher('/api/search', { params })
  );
  
  return { user, posts, userPosts, profile, searchResults };
}

// 缓存键工厂函数
const cacheKeys = {
  users: {
    all: () => ['users'],
    byId: (id) => ['users', id],
    posts: (id) => ['users', id, 'posts'],
    profile: (id) => ['users', id, 'profile'],
  },
  posts: {
    all: () => ['posts'],
    byCategory: (category) => ['posts', 'category', category],
    search: (query) => ['posts', 'search', query],
  },
};

缓存配置和重新验证

// 详细的缓存和重新验证配置
function SWRCacheConfiguration() {
  const { data, error, isLoading, mutate } = useSWR(
    '/api/user-data',
    fetcher,
    {
      // 重新验证策略
      revalidateOnFocus: true, // 窗口获得焦点时重新验证
      revalidateOnReconnect: true, // 网络重连时重新验证
      revalidateIfStale: true, // 数据过期时重新验证
      revalidateOnMount: true, // 组件挂载时重新验证
      
      // 自动刷新
      refreshInterval: 30000, // 每30秒自动刷新
      refreshWhenHidden: false, // 页面隐藏时不刷新
      refreshWhenOffline: false, // 离线时不刷新
      
      // 缓存相关
      dedupingInterval: 2000, // 2秒内相同请求去重
      focusThrottleInterval: 5000, // 焦点重新验证节流
      
      // 错误处理
      errorRetryCount: 3,
      errorRetryInterval: 5000,
      shouldRetryOnError: (error) => {
        // 404错误不重试
        return error.status !== 404;
      },
      
      // 性能优化
      suspense: false, // 是否使用Suspense
      fallbackData: undefined, // 初始数据
      keepPreviousData: true, // 保持之前的数据直到新数据到达
    }
  );
  
  return { data, error, isLoading, mutate };
}

手动缓存操作

import { mutate, cache } from 'swr';

// 缓存操作工具集
function SWRCacheOperations() {
  // 获取缓存数据
  const getCachedData = (key) => {
    return cache.get(key);
  };
  
  // 设置缓存数据
  const setCacheData = (key, data) => {
    mutate(key, data, false); // false = 不重新验证
  };
  
  // 更新缓存数据
  const updateCacheData = (key, updateFn) => {
    mutate(key, updateFn, false);
  };
  
  // 删除缓存
  const deleteCache = (key) => {
    cache.delete(key);
  };
  
  // 清空所有缓存
  const clearAllCache = () => {
    cache.clear();
  };
  
  // 批量更新相关缓存
  const batchUpdateCache = (updates) => {
    updates.forEach(({ key, data, revalidate = false }) => {
      mutate(key, data, revalidate);
    });
  };
  
  // 条件性缓存更新
  const conditionalUpdate = (pattern, updateFn) => {
    // 遍历所有缓存键
    for (const key of cache.keys()) {
      if (typeof key === 'string' && key.includes(pattern)) {
        const currentData = cache.get(key);
        const newData = updateFn(currentData);
        if (newData !== currentData) {
          mutate(key, newData, false);
        }
      }
    }
  };
  
  return {
    getCachedData,
    setCacheData,
    updateCacheData,
    deleteCache,
    clearAllCache,
    batchUpdateCache,
    conditionalUpdate,
  };
}

乐观更新实现

// 高级乐观更新模式
function useOptimisticSWR(key, fetcher, config = {}) {
  const { data, error, mutate } = useSWR(key, fetcher, config);
  
  // 乐观更新函数
  const optimisticUpdate = useCallback(async (
    updateData,
    requestFn,
    rollbackOnError = true
  ) => {
    // 保存当前数据用于回滚
    const previousData = data;
    
    try {
      // 立即更新UI
      mutate(updateData, false);
      
      // 执行实际请求
      const result = await requestFn();
      
      // 用服务器数据更新缓存
      mutate(result);
      
      return result;
    } catch (error) {
      // 错误时回滚数据
      if (rollbackOnError && previousData !== undefined) {
        mutate(previousData, false);
      }
      throw error;
    }
  }, [data, mutate]);
  
  return {
    data,
    error,
    mutate,
    optimisticUpdate,
  };
}

// 乐观更新示例
function TodoItem({ todo }) {
  const { data, optimisticUpdate } = useOptimisticSWR(
    ['todo', todo.id],
    () => fetchTodo(todo.id),
    { fallbackData: todo }
  );
  
  const toggleComplete = async () => {
    const optimisticData = {
      ...data,
      completed: !data.completed,
      updatedAt: new Date().toISOString(),
    };
    
    try {
      await optimisticUpdate(
        optimisticData,
        () => updateTodo(todo.id, { completed: !data.completed })
      );
      
      // 更新相关的todo列表缓存
      mutate(['todos'], (todos) => {
        return todos.map(t => 
          t.id === todo.id ? optimisticData : t
        );
      }, false);
      
    } catch (error) {
      toast.error('更新失败');
    }
  };
  
  return (
    <div className={`todo-item ${data.completed ? 'completed' : ''}`}>
      <input
        type="checkbox"
        checked={data.completed}
        onChange={toggleComplete}
      />
      <span>{data.title}</span>
    </div>
  );
}

缓存预填充和预取

// SWR 预取策略
function SWRPrefetching() {
  // 预取数据
  const prefetchData = useCallback((key, fetcher) => {
    // 只有缓存中没有数据时才预取
    if (!cache.has(key)) {
      mutate(key, fetcher(), false);
    }
  }, []);
  
  // 预填充列表数据中的详情
  const prefillDetailsFromList = useCallback((listData) => {
    listData.forEach(item => {
      const detailKey = ['item', item.id];
      if (!cache.has(detailKey)) {
        mutate(detailKey, item, false);
      }
    });
  }, []);
  
  // 智能预取下一页
  const prefetchNextPage = useCallback((currentPage, hasMore) => {
    if (hasMore) {
      const nextPageKey = ['posts', 'page', currentPage + 1];
      prefetchData(nextPageKey, () => fetchPosts(currentPage + 1));
    }
  }, [prefetchData]);
  
  return {
    prefetchData,
    prefillDetailsFromList,
    prefetchNextPage,
  };
}

// 鼠标悬停预取示例
function PostCard({ post }) {
  const { prefetchData } = SWRPrefetching();
  
  const handleMouseEnter = () => {
    // 预取文章详情
    prefetchData(['post', post.id], () => fetchPostDetail(post.id));
    
    // 预取作者信息
    prefetchData(['user', post.authorId], () => fetchUser(post.authorId));
  };
  
  return (
    <div 
      className="post-card"
      onMouseEnter={handleMouseEnter}
    >
      <h3>{post.title}</h3>
      <p>{post.excerpt}</p>
    </div>
  );
}

缓存持久化

// SWR 缓存持久化
function SWRPersistence() {
  // 本地存储配置
  const localStorageProvider = () => {
    // 在初始化时,我们将数据从 localStorage 恢复到 map 中
    const map = new Map(JSON.parse(localStorage.getItem('app-cache') || '[]'));
    
    // 在卸载应用程序之前,我们将所有数据写回 localStorage
    window.addEventListener('beforeunload', () => {
      const appCache = JSON.stringify(Array.from(map.entries()));
      localStorage.setItem('app-cache', appCache);
    });
    
    return map;
  };
  
  // 选择性持久化
  const persistentSWR = (key, fetcher, options = {}) => {
    return useSWR(key, fetcher, {
      ...options,
      // 只持久化特定数据
      use: [
        (useSWRNext) => (k, f, config) => {
          // 检查是否需要持久化
          if (config.persist) {
            return useSWRNext(k, f, {
              ...config,
              provider: localStorageProvider,
            });
          }
          return useSWRNext(k, f, config);
        }
      ]
    });
  };
  
  return { persistentSWR };
}

// 使用持久化缓存
function UserSettings() {
  const { data: settings } = useSWR(
    'user-settings',
    fetchUserSettings,
    {
      persist: true, // 标记为需要持久化
      revalidateOnMount: false,
    }
  );
  
  return <SettingsPanel settings={settings} />;
}

缓存监控和调试

// SWR 缓存监控
function SWRCacheMonitor() {
  const [cacheEntries, setCacheEntries] = useState([]);
  
  // 监控缓存变化
  useEffect(() => {
    const updateCacheInfo = () => {
      const entries = [];
      for (const [key, value] of cache.entries()) {
        entries.push({
          key: JSON.stringify(key),
          hasData: value.data !== undefined,
          hasError: value.error !== undefined,
          isLoading: value.isLoading,
          lastUpdate: value.lastUpdate,
        });
      }
      setCacheEntries(entries);
    };
    
    // 定期更新缓存信息
    const interval = setInterval(updateCacheInfo, 1000);
    updateCacheInfo();
    
    return () => clearInterval(interval);
  }, []);
  
  // 缓存操作工具
  const clearCache = () => {
    cache.clear();
    setCacheEntries([]);
  };
  
  const invalidateKey = (key) => {
    try {
      const parsedKey = JSON.parse(key);
      mutate(parsedKey);
    } catch (error) {
      console.error('Invalid key format:', error);
    }
  };
  
  return (
    <div className="cache-monitor">
      <div className="monitor-header">
        <h3>SWR 缓存监控</h3>
        <button onClick={clearCache}>清空缓存</button>
      </div>
      
      <div className="cache-stats">
        <p>缓存条目数: {cacheEntries.length}</p>
        <p>内存使用: {(JSON.stringify([...cache.entries()]).length / 1024).toFixed(2)} KB</p>
      </div>
      
      <table className="cache-table">
        <thead>
          <tr>
            <th>缓存键</th>
            <th>状态</th>
            <th>最后更新</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          {cacheEntries.map((entry, index) => (
            <tr key={index}>
              <td className="key-cell">{entry.key}</td>
              <td>
                <span className={`status ${entry.hasError ? 'error' : entry.hasData ? 'success' : 'loading'}`}>
                  {entry.hasError ? '错误' : entry.hasData ? '已缓存' : '加载中'}
                </span>
              </td>
              <td>{entry.lastUpdate ? new Date(entry.lastUpdate).toLocaleTimeString() : '-'}</td>
              <td>
                <button onClick={() => invalidateKey(entry.key)}>
                  重新验证
                </button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

// 缓存性能分析
function useCachePerformance() {
  const [stats, setStats] = useState({
    hits: 0,
    misses: 0,
    totalRequests: 0,
  });
  
  useEffect(() => {
    let hits = 0;
    let misses = 0;
    
    // 监听缓存事件(需要自定义实现)
    const originalFetcher = window.fetch;
    window.fetch = async (...args) => {
      const cacheKey = args[0];
      const hasCachedData = cache.has(cacheKey);
      
      if (hasCachedData) {
        hits++;
      } else {
        misses++;
      }
      
      setStats(prev => ({
        hits: prev.hits + (hasCachedData ? 1 : 0),
        misses: prev.misses + (hasCachedData ? 0 : 1),
        totalRequests: prev.totalRequests + 1,
      }));
      
      return originalFetcher(...args);
    };
    
    return () => {
      window.fetch = originalFetcher;
    };
  }, []);
  
  const hitRate = stats.totalRequests > 0 ? (stats.hits / stats.totalRequests * 100).toFixed(2) : 0;
  
  return {
    ...stats,
    hitRate: `${hitRate}%`,
  };
}

性能对比分析

Bundle 大小对比

// 包大小分析(gzip压缩后)
const bundleAnalysis = {
  'TanStack Query': {
    core: '~39KB',
    devtools: '~45KB',
    total: '~84KB'
  },
  'SWR': {
    core: '~24KB',
    devtools: 'N/A',
    total: '~24KB'
  }
};

// 性能基准测试
class PerformanceBenchmark {
  static async measureCacheHit(library, iterations = 1000) {
    const startTime = performance.now();
    
    for (let i = 0; i < iterations; i++) {
      // 模拟缓存命中
      await library.getFromCache(`test-key-${i % 100}`);
    }
    
    const endTime = performance.now();
    return endTime - startTime;
  }
  
  static async runComparison() {
    const results = {
      tanstackQuery: {
        cacheHit: await this.measureCacheHit(tanstackQuery),
        revalidation: await this.measureRevalidation(tanstackQuery)
      },
      swr: {
        cacheHit: await this.measureCacheHit(swr),
        revalidation: await this.measureRevalidation(swr)
      }
    };
    
    console.table(results);
    return results;
  }
}

内存缓存机制对比

是的,TanStack Query 和 SWR 默认都使用的是内存缓存(Memory Cache),但它们的行为、设计哲学和扩展能力有明显区别。

特性 SWR TanStack Query (React Query)
✅ 是否默认内存缓存 ✅ 是,基于 Map() ✅ 是,使用 QueryCache(内部也是内存)
缓存结构 Map 简单结构 结构复杂,包含 metadata、状态、observer 等
缓存元信息 ❌ 无元信息,只是简单的缓存值 ✅ 有完整 metadata(状态、时间戳、是否 stale、是否 active)
缓存作用域 全局共享(同一 App 内) 全局共享(但可通过 QueryClient 自定义作用域)
自动清除机制 ❌ 默认不会清除 ✅ 有 cacheTime,超过自动 GC
⏱ 是否支持过期控制 ❌ 不支持,需手动实现 TTL staleTime / cacheTime 内建
是否可自定义缓存 ✅ 支持 cacheProvider 替换(如 localStorage) ✅ 支持自定义 QueryCache,但需要更复杂的扩展

两者默认缓存底层是怎么实现的?

SWR

默认缓存结构:

const cache = new Map<string, any>()

你也可以通过 SWRConfig 替换:

 new Map() }}>
  


TanStack Query

默认缓存是通过 QueryClient 持有的 QueryCache 管理:

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      cacheTime: 5 * 60 * 1000, // 默认 5 分钟自动清理
      staleTime: 0              // 默认立即 stale
    }
  }
})

它的缓存结构是:

Map<queryHash, QueryInstance>

每个 QueryInstance 包含:

  • data
  • dataUpdatedAt
  • isStale
  • observers(组件)
  • status / fetchStatus
  • retryCount 等 metadata

两者默认都是基于“内存缓存”,但 TanStack Query 更复杂更智能,而 SWR 更轻量更灵活。

对比项 SWR TanStack Query
内存缓存结构 简单 key-value 结构化、有状态
生命周期控制 手动 自动(staleTime / cacheTime)
扩展性 非常灵活 高度可配置但复杂
使用场景 轻量级请求、组件驱动 复杂应用、分页、并发请求管理

如你要做缓存持久化、缓存同步跨 Tab、缓存加密等扩展,两者都可以做到,但实现路径略有不同

使用场景选择

选择决策树

简单到中等
复杂企业级
React新手
有经验
基础CRUD
复杂查询逻辑
选择数据获取库
项目复杂度
团队经验
TanStack Query
SWR
功能需求
丰富的查询功能
完善的开发工具
企业级特性
简单易用
轻量级
快速上手

具体使用场景

// 场景1: 简单的数据展示应用 - 推荐 SWR
function SimpleApp() {
  const ProfilePage = () => {
    const { data: user } = useSWR('/api/user', fetcher);
    const { data: posts } = useSWR('/api/posts', fetcher);
    
    return (
      <div>
        <UserCard user={user} />
        <PostsList posts={posts} />
      </div>
    );
  };
  
  return <ProfilePage />;
}

// 场景2: 复杂的企业级应用 - 推荐 TanStack Query
function EnterpriseApp() {
  const DashboardPage = () => {
    // 复杂的并行查询
    const userQuery = useQuery({
      queryKey: ['user'],
      queryFn: fetchUser
    });
    
    const projectsQuery = useQuery({
      queryKey: ['projects', userQuery.data?.id],
      queryFn: () => fetchProjects(userQuery.data.id),
      enabled: !!userQuery.data?.id
    });
    
    // 无限滚动
    const tasksQuery = useInfiniteQuery({
      queryKey: ['tasks'],
      queryFn: fetchTasks,
      getNextPageParam: (lastPage) => lastPage.nextCursor
    });
    
    return (
      <div className="dashboard">
        <UserSection user={userQuery.data} />
        <ProjectsGrid projects={projectsQuery.data} />
        <TasksList tasks={tasksQuery.data?.pages} />
      </div>
    );
  };
  
  return <DashboardPage />;
}

数据状态细化对比

特性 SWR TanStack Query
缓存是否“stale” 自动 + 模糊控制 明确用 staleTime 控制
缓存是否“过期” 默认不过期(除非页面刷新) cacheTime 过期回收
缓存是否清除 页面刷新或手动触发 cacheTime 自动清除
缓存元信息 无(以 value 为主) 有详细状态、时间戳等元信息

最佳实践

TanStack Query 最佳实践

// 1. 查询键管理
const queryKeys = {
  users: {
    all: ['users'] as const,
    lists: () => [...queryKeys.users.all, 'list'] as const,
    list: (filters: UserFilters) => 
      [...queryKeys.users.lists(), { filters }] as const,
    details: () => [...queryKeys.users.all, 'detail'] as const,
    detail: (id: string) => [...queryKeys.users.details(), id] as const,
  },
};

// 2. 自定义Hook封装
function useUser(userId: string) {
  return useQuery({
    queryKey: queryKeys.users.detail(userId),
    queryFn: () => api.get(`/users/${userId}`).then(res => res.data),
    staleTime: 5 * 60 * 1000,
    select: (data) => ({
      ...data,
      fullName: `${data.firstName} ${data.lastName}`
    })
  });
}

SWR 最佳实践

// 1. 自定义Hook模式
function useUser(userId?: string) {
  const { data, error, isLoading, mutate } = useSWR(
    userId ? `/api/users/${userId}` : null,
    {
      revalidateOnMount: true,
      dedupingInterval: 5000
    }
  );

  return {
    user: data,
    isLoading,
    isError: !!error,
    error,
    refresh: mutate
  };
}

// 2. 乐观更新工具
function useOptimisticUpdate() {
  return useCallback(async (
    key: string,
    updateFn: (data: any) => any,
    mutationFn: () => Promise<any>
  ) => {
    try {
      // 获取当前数据
      const currentData = cache.get(key);
      
      // 乐观更新
      const optimisticData = updateFn(currentData);
      mutate(key, optimisticData, false);
      
      // 执行实际请求
      const result = await mutationFn();
      
      // 更新为服务器数据
      mutate(key, result);
      
      return result;
    } catch (error) {
      // 回滚数据
      mutate(key);
      throw error;
    }
  }, []);
}

迁移指南

从 SWR 迁移到 TanStack Query

// SWR 代码
function UserProfile({ userId }) {
  const { data: user, error, mutate } = useSWR(
    `/api/users/${userId}`,
    fetcher,
    { revalidateOnFocus: false }
  );
  
  if (error) return <div>Error loading user</div>;
  if (!user) return <div>Loading...</div>;
  
  return <UserCard user={user} />;
}

// 迁移到 TanStack Query
function UserProfile({ userId }) {
  const { data: user, error, isLoading } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => api.get(`/users/${userId}`).then(res => res.data),
    refetchOnWindowFocus: false
  });
  
  if (error) return <div>Error loading user</div>;
  if (isLoading) return <div>Loading...</div>;
  
  return <UserCard user={user} />;
}

总结

TanStack Query 和 SWR 都是优秀的数据获取库,选择哪个主要取决于项目需求和团队偏好:

选择 TanStack Query 当:

  • 需要复杂的查询功能(无限滚动、并行查询、依赖查询)
  • 需要强大的开发工具和调试能力
  • 项目规模较大,需要企业级特性
  • 团队有足够的学习时间和经验

选择 SWR 当:

  • 项目相对简单,主要是基础的CRUD操作
  • 希望保持较小的包大小
  • 团队更偏向于简单直接的API
  • 快速原型开发或小型项目

无论选择哪个库,都应该:

  1. 建立良好的错误处理机制
  2. 合理配置缓存策略
  3. 实现适当的加载状态处理
  4. 考虑性能优化和监控
  5. 编写全面的测试覆盖

你可能感兴趣的:(服务端渲染,React.js,react.js,前端,前端框架)