前端开发必备:TanStack Query 完全指南 - 从入门到实践(1)

2,385 阅读3分钟

前言

大家好,我是 hyy,一个热爱技术分享的全栈工程师。

作为一个有着丰富经验的商业型 TypeScript 全栈开发者,我深知技术分享的重要性。我的座右铭是"深入浅出,知其然知其所以然",因为我始终相信,只有真正理解了基础,才能在技术的道路上走得更远。

在这篇文章中,我将带你深入浅出地了解 TanStack Router 这个强大的路由工具。无论你是初学者还是有经验的开发者,相信都能从中获得启发。

为什么选择 TanStack Query?

传统数据获取的痛点

  1. 重复请求处理

    • 多个组件请求相同数据导致重复请求
    • 数据更新后需要手动管理缓存
    • 页面切换后重新获取数据
  2. 全局状态管理复杂

    • Redux/Vuex 等需要手动处理异步状态
    • 需要编写大量的 actions/reducers
    • 服务端状态和客户端状态混合
  3. 性能问题

    • 未优化的重复请求影响性能
    • 大量数据缓存导致内存占用
    • 数据更新不及时

业务场景示例

1. 商品列表与详情

// 问题:列表页和详情页都需要商品数据,如何避免重复请求?
export function useProducts() {
  return useQuery({
    queryKey: ["products"],
    queryFn: async () => {
      const { data } = await axios.get("/api/products");
      return data;
    },
    staleTime: 1000 * 60 * 5, // 5分钟内不重新请求
  });
}

// 详情页复用缓存数据
export function useProduct(id: string) {
  return useQuery({
    queryKey: ["product", id],
    queryFn: async () => {
      const { data } = await axios.get(`/api/products/${id}`);
      return data;
    },
    initialData: () => {
      // 从列表缓存中获取初始数据
      const products = queryClient.getQueryData(["products"]);
      return products?.find((product) => product.id === id);
    },
  });
}

2. 实时购物车

// 问题:购物车数据需要实时更新,如何保持数据同步?
export function useCart() {
  return useQuery({
    queryKey: ["cart"],
    queryFn: async () => {
      const { data } = await axios.get("/api/cart");
      return data;
    },
    // 每分钟自动更新一次
    refetchInterval: 60000,
  });
}

export function useAddToCart() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: async (productId: string) => {
      const { data } = await axios.post("/api/cart/add", { productId });
      return data;
    },
    // 乐观更新
    onMutate: async (productId) => {
      await queryClient.cancelQueries({ queryKey: ["cart"] });
      const previousCart = queryClient.getQueryData(["cart"]);

      queryClient.setQueryData(["cart"], (old: Cart) => ({
        ...old,
        items: [...old.items, { productId, quantity: 1 }],
      }));

      return { previousCart };
    },
    onError: (err, variables, context) => {
      queryClient.setQueryData(["cart"], context.previousCart);
    },
  });
}

3. 分页搜索

// 问题:如何处理分页搜索并缓存结果?
export function useSearch(keyword: string, page: number) {
  return useQuery({
    queryKey: ["search", keyword, page],
    queryFn: async () => {
      const { data } = await axios.get("/api/search", {
        params: { keyword, page },
      });
      return data;
    },
    // 只有在关键词和页码都存在时才请求
    enabled: !!keyword && page > 0,
    // 保持之前页的数据
    keepPreviousData: true,
  });
}

4. 表单提交与验证

// 问题:如何处理表单提交和服务端验证?
export function useSubmitForm() {
  return useMutation({
    mutationFn: async (formData: FormData) => {
      const { data } = await axios.post("/api/submit", formData);
      return data;
    },
    onError: (error) => {
      if (error.response?.status === 422) {
        // 处理表单验证错误
        setFieldErrors(error.response.data.errors);
      }
    },
    onSuccess: () => {
      // 提交成功后刷新相关查询
      queryClient.invalidateQueries({ queryKey: ["user-data"] });
    },
  });
}

5. 实时数据同步

// 问题:如何处理WebSocket实时数据更新?
export function useRealtimeData() {
  const queryClient = useQueryClient();

  useEffect(() => {
    const ws = new WebSocket("ws://api.example.com");

    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      // 实时更新查询缓存
      queryClient.setQueryData(["realtime-data"], data);
    };

    return () => ws.close();
  }, []);

  return useQuery({
    queryKey: ["realtime-data"],
    queryFn: async () => {
      const { data } = await axios.get("/api/initial-data");
      return data;
    },
  });
}

解决的核心问题

  1. 数据同步

    • 自动处理数据过期和重新获取
    • 多组件间数据共享和同步
    • 乐观更新与回滚
  2. 性能优化

    • 智能请求去重
    • 数据预加载
    • 选择性更新
  3. 开发体验

    • 声明式数据获取
    • TypeScript 类型安全
    • 简化状态管理
  4. 用户体验

    • 加载状态智能处理
    • 错误边界处理
    • 离线数据支持

结语 & 加学习群 & 摸鱼群

感谢你读到这里!我是 hyy:

  • 🚀 一个有着丰富经验的商业型 TypeScript 全栈开发者
  • 💼 曾在小型外企、大型外包公司、创业公司等多个领域积累经验
  • 📝 掘金技术社区的活跃作者
  • 🎵 一个热爱电子音乐的 EDM 爱好者
  • 🎮 一个热衷于电子游戏的玩家
  • 🌟 一个乐于分享和交流的技术人

如果你也对前端开发充满热情,或者想要:

  • 一起学习和进步
  • 交流面试经验
  • 分享职业发展心得
  • 讨论金融、音乐、篮球、历史等兴趣爱好

点这个,有10000多名前端小伙伴在等着一起学习哦 --> 摸鱼沸点