react实现自定义轮询hook

1,125 阅读7分钟

一、使用requestAnimationFrame实现

    这段代码是一个自定义的 React Hook 函数 usePolling,用于实现轮询从服务器获取数据的功能。它使用 requestAnimationFrame 来控制轮询的时间间隔,以保持稳定的间隔时间。

    代码的主要部分:

  1. interface PollingOptions<T>:这是一个泛型接口,用于指定轮询的配置选项。其中包括 reqFunction,一个从服务器获取数据的异步函数,以及 interval,轮询的间隔时间(以毫秒为单位)。

  2. usePolling:这是自定义的 React Hook 函数,它接受一个对象参数 PollingOptions<T> 并返回一个状态变量 data

  3. useState:这是 React 的状态钩子函数,用于在组件中存储状态数据。在这里,data 是存储轮询获取到的数据的状态变量。

  4. useRef:这是 React 的引用钩子函数,用于在组件之间存储可变的值。在这里,animationFrameRef 是用于存储 requestAnimationFrame 的引用,startTimeRef 用于存储轮询开始的时间戳。

  5. startPolling:这是一个异步函数,用于发起轮询过程。它通过 requestAnimationFrame 来控制轮询的时间间隔,从而实现稳定的间隔时间。在每次轮询时,它会检查是否达到了轮询的间隔时间,如果是,则调用 reqFunction 来从服务器获取数据,并将数据存储在 data 状态变量中。然后,它会重置开始时间,并继续设置下一次轮询。

  6. useEffect:这是 React 的副作用钩子函数,用于在组件挂载和更新时执行副作用操作。在这里,它在组件挂载时调用 startPolling,开始轮询过程。另外,它还在组件卸载时清除动画帧,以避免内存泄漏。

    总结:这个自定义的 Hook 函数 usePolling 可以在 React 组件中使用,通过传入 reqFunctioninterval 来设置轮询的配置。它使用 requestAnimationFrame 来控制轮询的时间间隔,从而保持稳定的轮询间隔。每次轮询时,它会从服务器获取最新数据,并将其存储在 data 状态变量中供组件使用。

import { useEffect, useRef, useState } from 'react';

interface PollingOptions<T> {
  reqFunction: () => Promise<T>; // 从服务器获取数据的函数
  interval: number; // 轮询的间隔时间(毫秒)
}

function usePolling<T>({ reqFunction, interval }: PollingOptions<T>) {
  const [data, setData] = useState<T | null>(null);
  const animationFrameRef = useRef<number | null>(null);
  const startTimeRef = useRef<number>(0);

  // 开始轮询的函数
  const startPolling = async (timestamp: number) => {
    if (!startTimeRef.current) {
      startTimeRef.current = timestamp;
    }

    // 检查是否达到了轮询的间隔时间
    if (timestamp - startTimeRef.current >= interval) {
      try {
        const response = await reqFunction();
        setData(response);
      } catch (error) {
        // 处理错误
        console.error('轮询发生错误:', error);
      }

      // 重置开始时间
      startTimeRef.current = timestamp;
    }

    // 继续下一次轮询
    animationFrameRef.current = requestAnimationFrame(startPolling);
  };

  // 在组件挂载时开始轮询
  useEffect(() => {
    // 开始第一次轮询
    animationFrameRef.current = requestAnimationFrame(startPolling);

    // 组件卸载时清除动画帧
    return () => {
      if (animationFrameRef.current) {
        cancelAnimationFrame(animationFrameRef.current);
      }
    };
  }, [reqFunction, interval]);

  return data;
}

export default usePolling;

二、使用setTimeout实现

    使用setTimeout实现轮询与上述大同小异,让我们来解释代码的主要部分:

  1. useEffect:在这里,它在组件挂载时设置一个定时器 setTimeout,在指定的 interval 时间后执行 startPolling 函数。这样就实现了每隔一段时间就从服务器获取一次最新数据。另外,它还在组件卸载时清除定时器,以避免内存泄漏。

    总结:这个自定义的 Hook 函数 usePolling 使用 setTimeout 来实现轮询,每隔指定的 interval 时间就从服务器获取最新数据,并将其存储在 data 状态变量中供组件使用。在组件卸载时,会清除定时器,确保不会出现内存泄漏的问题。

import { useEffect, useRef, useState } from 'react';

interface PollingOptions<T> {
  reqFunction: () => Promise<T>; // 从服务器获取数据的函数
  interval: number; // 轮询的间隔时间(毫秒)
}

function usePolling<T>({ reqFunction, interval }: PollingOptions<T>) {
  const [data, setData] = useState<T | null>(null);
  const timeoutRef = useRef<number | null>(null);

  // 开始轮询的函数
  const startPolling = async () => {
    try {
      const response = await reqFunction();
      setData(response);
    } catch (error) {
      // 处理错误
      console.error('轮询发生错误:', error);
    }
  };

  // 在组件挂载时开始轮询
  useEffect(() => {
    timeoutRef.current = window.setTimeout(startPolling, interval);
    // 组件卸载时清除定时器
    return () => {
      if (timeoutRef.current) {
        window.clearTimeout(timeoutRef.current);
      }
    };
  }, [reqFunction, interval]);

  return data;
}

export default usePolling;

三、使用setInterval实现

    使用 setInterval 来控制轮询的时间间隔,以实现轮询:

  1. usePolling:这是自定义的 React Hook 函数,它接受一个对象参数 PollingOptions<T> 并返回一个状态对象 { data, setData }

  2. useState:这是 React 的状态钩子函数,用于在组件中存储状态数据。在这里,data 是存储轮询获取到的数据的状态变量,setData 是用于更新 data 的状态更新函数。

  3. useRef:这是 React 的引用钩子函数,用于在组件之间存储可变的值。在这里,pollingRef 是用于存储定时器的引用。

  4. useEffect:它在组件挂载时设置一个定时器 setInterval,在每隔指定的 interval 时间后执行 startPolling 函数。这样就实现了每隔一段时间就从服务器获取一次最新数据,并将其存储在 data 状态变量中供组件使用。另外,它还在组件卸载时清除定时器,以避免内存泄漏。

    总结:这个自定义的 Hook 函数 usePolling 可以在 React 组件中使用,通过传入 reqFunctioninterval 来设置轮询的配置。它使用 setInterval 来实现轮询,每隔指定的 interval 时间就从服务器获取最新数据,并将其存储在 data 状态变量中供组件使用。在组件卸载时,会清除定时器,确保不会出现内存泄漏的问题。同时,它返回了一个对象 { data, setData },使得组件可以访问轮询获取的数据,并且可以通过 setData 更新数据。

import { useEffect, useRef, useState } from 'react';

interface PollingOptions<T> {
  reqFunction: () => Promise<T>; // 从服务器获取数据的函数
  interval: number; // 轮询的间隔时间(毫秒)
}

function usePolling<T>({ reqFunction, interval }: PollingOptions<T>) {
  const [data, setData] = useState<T | null>(null);
  const pollingRef = useRef<number | null>(null);

  // 开始轮询的函数
  const startPolling = async () => {
    try {
      const response = await reqFunction();
      setData(response);
    } catch (error) {
      console.error('轮询错误:', error);
    }
  };

  // 组件挂载时立即开始轮询,并设置轮询的间隔时间
  useEffect(() => {
    pollingRef.current = window.setInterval(startPolling, interval);

    return () => {
      // 组件卸载时清除定时器
      if (pollingRef.current) {
        clearInterval(pollingRef.current);
      }
    };
  }, [reqFunction, interval]);

  return { data, setData };
}

export default usePolling;

使用方式

import React, { useEffect, useState } from 'react'
import usePolling from '@hook/usePolling';

function App() {

  const pollingInterval = 5000; //轮询间隔,单位为豪秒
  //轮询队伍状态
  const getPollingData = async () => {
    const res = await getData({
      uuid: uuid,
    })
    return res
  }

  const data = usePolling({reqFunction: getPollingData, interval: pollingInterval})

  useEffect(() => {
    console.log('当前data:', data)
  }, [data]);

  return (
    <div className="app">
      {data}
    </div>
  );
}

export default App;

以上三种实现方式的优劣

以上三种实现轮询的方式分别是:

  1. 使用 setTimeout 方式:在 usePolling 中使用 setTimeout 来设置每次轮询的间隔时间。

  2. 使用 setInterval 方式:在 usePolling 中使用 setInterval 来设置轮询的间隔时间,使用 clearInterval 清除定时器。

  3. 使用 requestAnimationFrame 方式:在 usePolling 中使用 requestAnimationFrame 来设置每次轮询的间隔时间,使用 cancelAnimationFrame 清除动画帧。

区别和优劣势:

  1. 使用 setTimeout 方式:

    优势:

    • 简单易用,代码实现相对直接。
    • 可以自由地设置每次轮询的间隔时间,灵活性较高。

    劣势:

    • 由于 setTimeout 的精确度有限,可能会导致轮询的间隔时间不稳定,尤其在页面存在其他任务或资源负载较高时。
    • 在每次轮询时都需要设置新的 setTimeout,可能导致多次重复的计时器创建。
  2. 使用 setInterval 方式:

    优势:

    • 相比 setTimeoutsetInterval 可以更稳定地设置轮询的间隔时间,因为它是周期性地触发回调函数。
    • 代码相对简单,使用 clearInterval 可以轻松清除定时器。

    劣势:

    • 仍然有一定的时间精度限制,可能会受到页面负载的影响导致轮询不够精确。
    • 如果某次轮询任务耗时较长,可能导致下一次轮询被延迟执行,可能不是最佳的选择。
  3. 使用 requestAnimationFrame 方式:

    优势:

    • requestAnimationFrame 的精确度较高,它在浏览器重绘之前执行回调函数,通常为每秒60次(60FPS)。
    • 适用于需要较高精确度的动画或定时任务,能够避免累积延迟的问题。
    • 不会导致页面在后台标签中执行时降低性能,因为它会在页面活跃时才触发回调。

    劣势:

    • requestAnimationFrame 的执行时间通常是在16.7毫秒(1秒/60)左右,无法直接设置其他间隔时间。
    • 如果需要自定义较长的轮询间隔,可能需要在回调函数中自行实现时间控制。

    综合来说,如果需要较高精确度和性能较好的定时任务,可以考虑使用 requestAnimationFrame 方式。如果简单的轮询,并且间隔时间不是特别关键,可以使用 setInterval。而 setTimeout 方式则在一些特定场景下使用较多,但需要特别注意间隔时间的不稳定性。