React - 使用 AbortController 和 addEventListener 创建自定义钩子来处理在线和离线事件

使用 AbortController 与 removeEventListener

使用 AbortController

import { useEffect } from "react"

export function useOnlineStatus(onlineCallback: () => void, offlineCallback: () => void) {
  useEffect(() => {
    const controller = new AbortController()
    window.addEventListener("online", onlineCallback, { signal: controller.signal })
    window.addEventListener("offline", offlineCallback, { signal: controller.signal })

    return () => {
      controller.abort()
    }
  }, [onlineCallback, offlineCallback])
}

对比

使用 removeEventListener

import { useEffect } from 'react';

export function useOnlineStatus(
  onlineCallback: () => void,
  offlineCallback: () => void
) {
  useEffect(() => {
    window.addEventListener('online', onlineCallback);
    window.addEventListener('offline', offlineCallback);

    return () => {
      window.removeEventListener('online', onlineCallback);
      window.removeEventListener('offline', offlineCallback);
    };
  }, [onlineCallback, offlineCallback]);
}

可读性

`useEffect` 的清理函数(每次重新渲染和卸载)可以简单地调用 `controller.abort()`

...

return () => {
    controller.abort()
}

...

问题:呈现错误的在线状态

  • https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine
  • 例如,

    使用组件 App 中钩子 useOnlineStatus 返回的变量

    useOnlineStatus.ts

    import { useEffect } from "react"
    
    export function useOnlineStatus(onlineCallback: () => void, offlineCallback: () => void) {
      useEffect(() => {
        const controller = new AbortController()
        window.addEventListener("online", onlineCallback, { signal: controller.signal })
        window.addEventListener("offline", offlineCallback, { signal: controller.signal })
    
        return () => {
          controller.abort()
        }
      }, [onlineCallback, offlineCallback])
    
      // return the window online status
      return navigator.onLine;
    }

    应用程序.tsx

    import React, {useCallback} from 'react';
    import { useOnlineStatus } from './hook';
    
    export function App(props) {
      const handleOnline = useCallback(() => {
        console.log('online');
      }, []);
      const handleOffline = useCallback(() => {
        console.log('offline');
      }, []);
      const isOnline = useOnlineStatus(handleOnline, handleOffline);
      return (
        

    Hello React.

    Start editing to see some magic happen!

    Online status: {isOnline.toString()}

    ); }

    但是,在 Chrome DevTools 的“网络”标签上切换“离线”和“无限制”时,组件应用程序不会显示正确的在线状态“isOnline”。

    看到当连接状态为在线时,`isOnline`的值为false:

    Image description

    根本原因

    因为只有当组件 App 被渲染时,调用钩子 `useOnlineStatus` 的返回值 `navigator.onLine` 才会赋值给变量 `isOnline`。但是当在线状态改变时,组件 App 并不会重新渲染,窗口中显示的会是旧状态的 `isOnline`,而不是当前的 `navigator.onLine` 值。

    解决方案

    更新“useOnlineStatus”钩子以返回状态变量而不是“navigator.onLine”。

    当向目标(窗口)传递`online`事件时,会分别调用回调函数`onlineCallback`和`set`执行任务、更新状态变量;当向目标(窗口)传递`offline`事件时,工作原理类似。

    声明状态变量“isOnline”

    const [isOnline, setIsOnline] = useState(true);

    完整代码

    import { useEffect, useState, useCallback } from 'react';
    
    export function useOnlineStatus(
      onlineCallback: () => void,
      offlineCallback: () => void
    ) {
      const [isOnline, setIsOnline] = useState(true);
      const handleOnline = useCallback(() => {
        onlineCallback();
        setIsOnline(true);
      }, []);
      const handleOffline = useCallback(() => {
        offlineCallback();
        setIsOnline(false);
      }, []);
    
      useEffect(() => {
        const controller = new AbortController();
        window.addEventListener('online', handleOnline, {
          signal: controller.signal,
        });
        window.addEventListener('offline', handleOffline, {
          signal: controller.signal,
        });
    
        return () => {
          controller.abort();
        };
      }, [onlineCallback, offlineCallback]);
      return isOnline;
    }

    这样,当在线/离线状态发生变化时,使用 `useOnlineStatus` hook 的组件 App 就会重新渲染并显示正确的在线状态。