首页 > web前端 > js教程 > React 中用于异步数据获取和缓存的轻量级 Hook 展示

React 中用于异步数据获取和缓存的轻量级 Hook 展示

Mary-Kate Olsen
发布: 2025-01-07 18:36:41
原创
520 人浏览过

Showcase of a Lightweight Hook for Async Data Fetching & Caching in React

大家好!

我一直在开发一个轻量级的 React hook,我称之为 useAsync,它模仿 React Query 的一些基本功能(如获取、缓存、重试等),但以更紧凑的方式,易于定制的包。下面是其内部工作原理的快速分解,引用了相关的代码部分。如果您想查看完整代码,请前往存储库:

GitHub 上的完整源代码
该钩子也可以在 npm 上作为 api-refetch.

使用

为什么要自己制作钩子?

虽然 React Query 和 SWR 都是很棒的库,但出于以下几个原因,我想要一种更实用的方法:

  1. 轻量化

    虽然 React Query 和 SWR 功能丰富,但它们可能相对较大(React Query ~2.2 MB,SWR ~620 kB)。 api-refetch 大约 250 kB,使其非常适合较小的应用程序,其中包大小是一个大问题。该挂钩意味着作为另一个库(Intlayer)的依赖项安装。因此,解决方案的大小是一个重要的考虑因素。

  2. 易于定制和优化
    我需要一些特定的功能,例如从本地存储存储/获取数据使用简单的方法管理并行请求
    通过克隆存储库将代码直接复制到您的项目中,您可以删除任何不需要的功能并仅保留您需要的功能。这不仅可以减少捆绑包大小,还可以最大限度地减少不必要的重新渲染和增加,为您提供根据您的特定要求量身定制的更精简、性能更高的解决方案。

  3. 无所需提供商

    我想避免使用 Context Provider 来使钩子全局化,并使其使用尽可能简单。所以我根据 Zustand 商店制作了 hooke 的一个版本(参见下面的示例)。

  4. 学习练习

    从头开始构建异步库是理解并发、缓存和状态管理内部结构的绝佳方法。

简而言之,滚动我自己的钩子是一个机会精确磨练我需要的功能(并跳过我不需要的功能),同时保持库小且易于理解。

涵盖的功能

React 钩子管理:

  • 获取和状态管理:处理加载、错误、成功和获取状态。
  • 缓存和存储:可选择缓存数据(通过底层的 React 状态或 Zustand)并提供本地存储支持。
  • 重试和重新验证:可配置的重试限制和自动重新验证间隔。
  • 激活和失效:根据其他查询或状态自动激活和失效查询。示例:用户登录时自动获取一些数据,并在用户注销时使其失效。
  • 并行组件挂载获取:当多个组件同时挂载时,防止对同一资源同时发出多个请求。

代码如何运作

以下是 api-refetch 中的要点以及对 useAsync.tsx 中代码相关部分的简短引用。

1. 并行安装的获取和处理

  • 代码片段
  // This map stores any in-progress Promise to avoid sending parallel requests
  // for the same resource across multiple components.
  const pendingPromises = new Map();

  const fetch: T = async (...args) => {
    // Check if a request with the same key + args is already running
    if (pendingPromises.has(keyWithArgs)) {
      return pendingPromises.get(keyWithArgs);
    }

    // Otherwise, store a new Promise and execute
    const promise = (async () => {
      setQueryState(keyWithArgs, { isLoading: true });

      // ...perform fetch here...
    })();

    // Keep it in the map until it resolves or rejects
    pendingPromises.set(keyWithArgs, promise);
    return await promise;
  };
登录后复制
登录后复制
  • 说明:在这里,我们将任何正在进行的提取存储在pendingPromises 映射中。当两个组件尝试同时获取相同的资源(通过具有相同的 keyWithArgs)时,第二个组件只会重用正在进行的请求,而不是进行重复的网络调用。

2. 重新验证

  • 代码片段
  // Handle periodic revalidation if caching is enabled
  useEffect(
    () => {
      if (!revalidationEnabled || revalidateTime <= 0) return; // Revalidation is disabled
      if (!isEnabled || !enabled) return; // Hook is disabled
      if (isLoading) return; // Fetch is already in progress
      if (!isSuccess || !fetchedDateTime) return; // Should retry either of revalidate
      if (!(cacheEnabled || storeEnabled)) return; // Useless to revalidate if caching is disabled

      const timeout = setTimeout(() => {
        fetch(...storedArgsRef.current);
      }, revalidateTime);

      return () => clearTimeout(timeout);
    },
    [
      /* dependencies */
    ]
  );
登录后复制
登录后复制
  • 说明:每当您启用重新验证时,api-refetch 都会检查缓存的数据是否早于指定的 revalidateTime。如果是,数据会在后台自动重新获取,以使您的 UI 保持同步,无需额外的手动触发。

3. 重试逻辑

  • 代码片段
  useEffect(
    () => {
      const isRetryEnabled = errorCount > 0 && retryLimit > 0;
      const isRetryLimitReached = errorCount > retryLimit;

      if (!isEnabled || !enabled) return; // Hook is disabled
      if (!isRetryEnabled) return; // Retry is disabled
      if (isRetryLimitReached) return; // Retry limit has been reached
      if (!(cacheEnabled || storeEnabled)) return; // Useless to retry if caching is disabled
      if (isLoading) return; // Fetch is already in progress
      if (isSuccess) return; // Hook has already fetched successfully

      const timeout = setTimeout(() => {
        fetch(...storedArgsRef.current);
      }, retryTime);

      return () => clearTimeout(timeout);
    },
    [
      /* dependencies */
    ]
  );
登录后复制
  • 解释:发生错误时,挂钩会计算已发生的失败尝试次数。如果它仍然低于 retryLimit,它会自动等待 retryTime 毫秒,然后再重试。此过程将持续进行,直到成功获取数据或达到重试限制。

4. 自动获取

  • 代码片段
  // Auto-fetch data on hook mount if autoFetch is true
  useEffect(
    () => {
      if (!autoFetch) return; // Auto-fetch is disabled
      if (!isEnabled || !enabled) return; // Hook is disabled
      if (isFetched && !isInvalidated) return; // Hook have already fetched or invalidated
      if (isLoading) return; // Fetch is already in progress

      fetch(...storedArgsRef.current);
    },
    [
      /* dependencies */
    ]
  );
登录后复制
  • 说明:将 autoFetch 设置为 true 时,钩子将在组件安装后立即自动运行异步函数,非常适合您始终希望加载数据的“即发即忘”场景。

查看 GitHub 上的完整源代码

查看完整代码,其中包括本地存储逻辑、查询失效等:

  • 完整源代码

如果您有兴趣,请随意尝试、报告问题或做出贡献。非常感谢任何反馈!

使用示例

安装

复制代码或编码(repo)[https://github.com/aymericzip/api-refetch]

或者

  // This map stores any in-progress Promise to avoid sending parallel requests
  // for the same resource across multiple components.
  const pendingPromises = new Map();

  const fetch: T = async (...args) => {
    // Check if a request with the same key + args is already running
    if (pendingPromises.has(keyWithArgs)) {
      return pendingPromises.get(keyWithArgs);
    }

    // Otherwise, store a new Promise and execute
    const promise = (async () => {
      setQueryState(keyWithArgs, { isLoading: true });

      // ...perform fetch here...
    })();

    // Keep it in the map until it resolves or rejects
    pendingPromises.set(keyWithArgs, promise);
    return await promise;
  };
登录后复制
登录后复制

快速示例

  // Handle periodic revalidation if caching is enabled
  useEffect(
    () => {
      if (!revalidationEnabled || revalidateTime <= 0) return; // Revalidation is disabled
      if (!isEnabled || !enabled) return; // Hook is disabled
      if (isLoading) return; // Fetch is already in progress
      if (!isSuccess || !fetchedDateTime) return; // Should retry either of revalidate
      if (!(cacheEnabled || storeEnabled)) return; // Useless to revalidate if caching is disabled

      const timeout = setTimeout(() => {
        fetch(...storedArgsRef.current);
      }, revalidateTime);

      return () => clearTimeout(timeout);
    },
    [
      /* dependencies */
    ]
  );
登录后复制
登录后复制

就是这样!尝试一下,然后让我知道进展如何。 GitHub 上非常欢迎提供反馈、问题或贡献。

GitHub: api-refetch

编码愉快!

以上是React 中用于异步数据获取和缓存的轻量级 Hook 展示的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板