N 个值得一看的前端Hooks

笔者总结一些工作中常用的Hooks,非常值得一看

useMount

useMount 不是 React 官方提供的 Hook,而是一个常见的自定义 Hook,通常用于执行组件首次渲染时的副作用。

它的主要作用是模拟 componentDidMount 生命周期方法------也就是在组件第一次渲染完成后执行某些代码。你可以将 useEffect 配合空数组 [] 来实现类似的功能,但是 useMount 是一种封装,提供了更简洁的语法。

以下是一个简单的 useMount 实现示例:

js 复制代码
function useMount(callback) {
  if (typeof callback !== "function") {
    throw new Error("callback must be a function");
  }

  useEffect(() => {
    callback();
  }, []);
}

useUnmount

useUnmount 是一个类似于 useMount 的自定义 Hook,通常用于在组件卸载时执行某些清理操作。它相当于模拟 componentWillUnmount 生命周期方法,即在组件从 DOM 中移除时触发的副作用。

React 本身并没有提供 useUnmount,但是你可以通过结合 useEffect 来实现这个功能。通常我们使用 useEffect 的清理函数来处理组件卸载时的逻辑。

以下是一个简单的 useUnmount 实现示例:

js 复制代码
function useUnmount(cb) {
  if (typeof cb !== "function") {
    throw new Error("callback must be a function");
  }

  useEffect(() => {
    return () => {
      cb?.();
    };
  }, []);
}

useDebounce

useDebounce 是一个自定义的 React Hook,用来处理输入或其他事件的防抖(debouncing)操作。防抖的基本思路是,只有在事件停止触发一段时间后才执行某个操作。这对于减少频繁的 API 请求、搜索框输入的实时请求、滚动监听等非常有用。

在没有防抖的情况下,用户每次输入都会立即触发一次操作(例如网络请求),这可能会导致性能问题。而使用防抖可以确保只有在用户停止输入一段时间后,才进行实际的处理,从而减少不必要的操作。

以下是一个简单的 useDebounce 实现示例:

js 复制代码
function useDebounce(value, delay = 500) {
  const [debouncedValue, setDebouncedValue] = React.useState(value);

  React.useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(timer);
    };
  }, [value, delay]);

  return debouncedValue;
}

useThrottle

useThrottle 是一个自定义的 React Hook,用于 限制函数的调用频率 。它的作用是使某个函数在特定时间内只能执行一次,而无论该函数被调用多少次。这个概念通常称为 节流(throttling) ,它主要用于优化性能,尤其是在用户频繁触发某些操作时,比如滚动事件、输入框的内容变化、窗口大小调整等。

什么时候使用 useThrottle

  • 滚动事件:用户滚动页面时,可能会触发很多次事件,而你只希望在一定时间内响应一次滚动。
  • 窗口大小调整 :调整窗口大小时,频繁触发 resize 事件,使用节流可以减少不必要的重新计算。
  • 输入框输入 :当用户输入时,频繁触发 onChange 事件,节流可以减少每次输入后的处理次数,避免过度渲染。

以下是一个简单的 useThrottle 实现示例:

js 复制代码
useThrottle(value, interval = 500) {
  const [throttledValue, setThrottledValue] = React.useState(value);
  const lastUpdated = React.useRef(null);

  React.useEffect(() => {
    const now = Date.now();

    // 如果上次更新时间存在且当前时间大于等于上次更新时间加上间隔时间
    if (lastUpdated.current && now >= lastUpdated.current + interval) {
      lastUpdated.current = now;
      setThrottledValue(value);
    } else {
      // 否则设置一个定时器在间隔时间后更新值
      const id = window.setTimeout(() => {
        lastUpdated.current = now;
        setThrottledValue(value);
      }, interval);

      // 清除定时器
      return () => window.clearTimeout(id);
    }
  }, [value, interval]);

  return throttledValue;
}

useClickAway

useClickAway 是一个自定义的 React Hook,通常用于处理点击外部区域时的事件。它帮助你检测用户是否点击了某个元素之外的区域,并在此事件发生时执行特定的操作。常见的应用场景包括关闭弹出框、菜单、模态框等 UI 元素,或者处理点击外部区域的其他逻辑。

React 没有提供内置的 useClickAway,但我们可以通过监听点击事件来实现这个功能。通常,useClickAway 会监听整个文档的点击事件,并判断点击是否发生在指定的目标元素外部。

以下是一个简单的 useClickAway 实现示例:

js 复制代码
function useClickAway(cb) {
  const ref = useRef(null);
  const cbRef = useRef(cb);

  useLayoutEffect(() => {
    cbRef.current = cb;
  });

  useEffect(() => {
    const handler = (event) => {
      if (ref.current && !ref.current.contains(event.target)) {
        cbRef.current?.(event);
      }
    };

    document.addEventListener("mousedown", handler);
    document.addEventListener("touchstart", handler);

    return () => {
      document.removeEventListener("mouseup", handler);
      document.removeEventListener("touchstart", handler);
    };
  }, []);

  return ref;
}

useEventBus

useEventBus 是一个自定义的 React Hook,用于实现事件总线(Event Bus)的功能。事件总线是一种用于组件之间通信的模式,它允许不同组件之间通过发布和订阅事件来进行交互,而不需要直接通过父子组件的传递 props 或上下文来实现。

useEventBus 的基本思想是:组件可以"订阅"某个事件,并在事件发生时执行回调函数;同样,组件也可以"发布"事件,通知其他订阅了该事件的组件。

以下是一个简单的 useEventBus 实现示例:

js 复制代码
import { useRef, useCallback } from "react";

const eventsMap = new Map();

function useEventBus(eventName) {
  const events = useRef(eventsMap);
  const off = useCallback(
    (callback) => {
      const currentCallbacks = events.current.get(eventName);
      if (currentCallbacks) {
        events.current.set(
          eventName,
          currentCallbacks.filter((cb) => cb !== callback)
        );
      }
    },
    [eventName]
  );

  const on = useCallback(
    (callback) => {
      const currentCallbacks = events.current.get(eventName) || [];
      currentCallbacks.push(callback);
      events.current.set(eventName, currentCallbacks);
      return () => off(callback);
    },
    [eventName, off]
  );

  const emit = useCallback(
    (data) => {
      const currentCallbacks = events.current.get(eventName);
      if (currentCallbacks) {
        currentCallbacks.forEach((callback) => callback(data));
      }
    },
    [eventName]
  );

  const once = useCallback(
    (callback) => {
      const onceCallback = (data) => {
        callback(data); 
        off(onceCallback);
      };
      on(onceCallback); 
    },
    [off, on]
  );
  
  const reset = useCallback(() => {
    events.current.set(eventName, []);
  }, [eventName]);

  return {
    on,
    off,
    emit,
    once,
    reset,
  };
}

useElementSize

useElementSize 是一个自定义的 React Hook,用于获取和监听元素的尺寸变化(宽度和高度)。通常,它可以帮助你在元素大小发生变化时,自动更新对应的状态,从而触发相应的 UI 更新。这个 Hook 常常用于响应式布局、动态调整界面元素大小或处理元素大小变化的场景。

React 没有内置的 useElementSize,但你可以通过 ResizeObserver 来实现这种功能。ResizeObserver 是一种浏览器原生的 API,专门用于监听元素的大小变化。

以下是一个简单的 useElementSize 实现示例:

js 复制代码
useElementSize(target) {
  const [width, setWidth] = useState(0);
  const [height, setHeight] = useState(0);

  useEffect(() => {
    const observer = new ResizeObserver((entries) => {
      for (let entry of entries) {
        setWidth(entry.contentRect.width);
        setHeight(entry.contentRect.height);
      }
    });

    observer.observe(target.current);

    return () => {
      observer.disconnect();
    };
  }, [target]);

  return { width, height };
}

useCounter

useCounter 是一个常见的自定义 React Hook,用于处理计数器功能(如增加、减少计数)。它封装了对状态的管理和更新,简化了计数器的实现。你可以通过传递初始值和增减步长来使用它,并且它通常会暴露一组操作方法,比如 incrementdecrementreset 等。

useCounter 的作用是让你更方便地管理计数状态,避免重复编写增加、减少和重置计数的代码。

以下是一个简单的 useCounter 实现示例:

js 复制代码
useCounter(initialValue = 0, step = 1) {
  const [count, setCount] = useState(initialValue);

  const increment = useCallback(
    () => setCount((prevCount) => prevCount + step),
    [step]
  );
  const decrement = useCallback(
    () => setCount((prevCount) => prevCount - step),
    [step]
  );

  const reset = useCallback(() => setCount(initialValue), [initialValue]);

  return { count, increment, decrement, reset };
}

useMemoizedFn

useMemoizedFn 是一个自定义的 React Hook,用于优化函数的性能,确保在组件重新渲染时,某些函数不会被重新创建。它的作用是"记住"函数的实例,并确保函数的引用在渲染过程中保持稳定,从而避免不必要的重新渲染或者性能瓶颈。

在 React 中,每当组件重新渲染时,所有的函数都会被重新创建,这可能会导致不必要的性能问题,特别是在函数作为依赖传递给 useEffectuseCallback 或子组件的 props 时。useMemoizedFn 可以帮助解决这个问题,确保函数在每次渲染时保持相同的引用。

以下是一个简单的 useMemoizedFn 实现示例:

js 复制代码
function useMemoizedFn(fn) {
  const ref = useRef(fn);

  // 确保 ref 始终指向最新的 fn
  ref.current = fn;

  return (...args) => ref.current(...args);
}

useFocusTab

useFocusTab 是一个自定义的 React hook,用于 管理和控制浏览器选项卡(Tab) 的焦点和切换行为。

以下是一个简单的 useFocusTab 实现示例:

js 复制代码
function useFocusTab(cb) {
  const cbRef = useRef(cb);
  cbRef.current = cb;

  useEffect(() => {
    const handleVisibilityChange = () => {
      if (!document.hidden) {
        cbRef.current?.();
      }
    };

    document.addEventListener("visibilitychange", handleVisibilityChange);

    return () => {
      document.removeEventListener("visibilitychange", handleVisibilityChange);
    };
  }, []);
}

useInView

useInView 是一个常用的 React hook,通常用于检测元素是否进入视口(viewport)或者是否在用户当前可见的区域内。这在实现懒加载、滚动触发事件、或视差滚动等场景中非常有用。

useInView 主要帮助你判断一个 DOM 元素是否已经进入或离开视口,从而触发相应的动作。这可以用于:

  • 懒加载图片:当图片进入视口时才加载。
  • 滚动监听:当某个元素进入或离开视口时触发特定的动画或事件。
  • 触发特效:例如,用户滚动到特定位置时显示某些动画效果。

以下是一个简单的 useInView 实现示例:

js 复制代码
function useInView(options) {
  const ref = useRef(null);
  const optionsRef = useRef(options);

  optionsRef.current = options;

  const [inView, setInView] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver(([entry]) => {
      setInView(entry.isIntersecting);
    });

    observer.observe(ref.current, {
      rootMargin: "0px",
      threshold: 1.0,
      ...optionsRef.current,
    });

    return () => {
      observer.disconnect();
    };
  }, []);

  return { ref, inView };
}

useLocalStorage

useLocalStorage 是一个自定义的 React hook,用于简化与浏览器 localStorage 的交互。它可以让你轻松地存储、读取和更新浏览器的本地存储(localStorage)数据,同时保持 React 组件的状态同步。

localStorage 是一个 Web API,允许你将数据以键值对的形式存储在浏览器中,这些数据在页面刷新或浏览器关闭后依然保留。它是一个同步的存储机制,数据的生命周期通常为浏览器会话周期(直到用户清除浏览器缓存或者通过编程手段删除数据)。

useLocalStorage hook 基本上封装了 localStorage,使其更方便地与 React 组件的状态结合使用。这样你就可以像使用 useState 一样轻松操作本地存储。

以下是一个简单的 useLocalStorage 实现示例:

js 复制代码
function useLocalStorage(key, initialValue) {
  // 获取初始值
  const [storedValue, setStoredValue] = useState(() => {
    try {
      // 从 localStorage 中读取数据
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error("Error reading localStorage key:", key, error);
      return initialValue;
    }
  });

  // 设置 localStorage 的值
  const setValue = useCallback(
    (value) => {
      try {
        const valueToStore =
          value instanceof Function ? value(storedValue) : value;
        setStoredValue(valueToStore);
        // 更新 localStorage
        window.localStorage.setItem(key, JSON.stringify(valueToStore));
      } catch (error) {
        console.error("Error setting localStorage key:", key, error);
      }
    },
    [key, storedValue]
  );

  const removeValue = useCallback(() => {
    try {
      window.localStorage.removeItem(key);
      setStoredValue(null);
    } catch (error) {
      console.error("Error removing localStorage key:", key, error);
    }
  }, [key]);

  return { storedValue, setValue, removeValue };
}

useScrollToBottom

useScrollToBottom 是一个自定义的 React hook,通常用于实现自动滚动到容器的底部功能,常见于聊天窗口、消息列表等场景。当容器内容变化(例如有新消息加入)时,它会自动将视图滚动到底部。

以下是一个简单的 useScrollToBottom 实现示例:

js 复制代码
function useScrollToBottom(parentRef, childRef) {
  const [parentHeight, setParentHeight] = useState(0);

  const [childHeight, setChildHeight] = useState(0);

  useEffect(() => {
    const observer = new ResizeObserver((entries) => {
      for (let entry of entries) {
        console.log("parent height", entry.contentRect.height);
        setParentHeight(entry.contentRect.height);
      }
    });

    observer.observe(parentRef.current);

    return () => {
      observer.disconnect();
    };
  }, [parentRef]);

  useEffect(() => {
    const observer = new ResizeObserver((entries) => {
      for (let entry of entries) {
        console.log("child height", entry.contentRect.height);
        setChildHeight(entry.contentRect.height);
      }
    });

    observer.observe(childRef.current);

    return () => {
      observer.disconnect();
    };
  }, [childRef]);

  useEffect(() => {
    const parent = parentRef.current;
    const child = childRef.current;

    if (parent && child) {
      parent.scrollTop = child.scrollHeight;
    }
  }, [parentRef, childRef]);

  const goToBottom = useCallback(() => {
    if (childHeight > parentHeight) {
      parentRef.current?.scrollTo({
        top: childHeight - parentHeight,
        behavior: "smooth",
      });
    }
  }, [childHeight, parentHeight, parentRef]);

  useEffect(() => {
    goToBottom();
  }, []);

  return { goToBottom };
}

useWindowSize

useWindowSize 是一个常见的自定义 React hook,通常用于获取和监听浏览器窗口的大小(即宽度和高度)。它可以帮助你实现响应式设计或根据窗口尺寸动态调整页面布局或元素样式。

这个 hook 基本上监听 windowresize 事件,确保在窗口大小变化时,组件能够自动更新其状态,并提供最新的窗口尺寸。

以下是一个简单的 useWindowSize 实现示例:

js 复制代码
function useWindowSize() {
  const [width, setWidth] = useState(window.innerWidth);
  const [height, setHeight] = useState(window.innerHeight);

  useEffect(() => {
    const observer = new ResizeObserver((entries) => {
      for (let entry of entries) {
        setWidth(entry.contentRect.width);
        setHeight(entry.contentRect.height);
      }
    });

    observer.observe(document.documentElement);

    return () => {
      observer.disconnect();
    };
  }, []);

  return { width, height };
}

useDrag && useDrop

useDraguseDrop 是 React hook,用于实现 拖拽(drag-and-drop) 功能。这两个 hook 通常与 React DnD(Drag and Drop)库一起使用,允许你在 React 应用中创建交互式的拖拽效果,比如拖动列表项、拖动文件等。

这两个 hook 提供了一种简洁的方式来处理拖拽操作,包括拖动、放置、更新组件状态等。它们通常一起使用:useDrag 用于设置拖动源,useDrop 用于设置放置目标。

useQueue

useQueue 通常是在编程或前端开发中使用的一个自定义 Hook,用于处理队列相关的操作。在 React 或 JavaScript 的一些框架中,它可能被用来管理队列的状态或逻辑。

具体来说,useQueue 可能会执行以下任务:

  1. 管理队列的状态:存储和管理一个数据结构(如数组或链表),表示当前的队列。
  2. 操作队列:提供像入队(enqueue)和出队(dequeue)这样的操作函数。
  3. 处理异步任务 :在异步场景下,useQueue 可以帮助管理任务的顺序执行,确保任务按正确的顺序处理。

举个例子,假设你需要管理一个需要按顺序执行的异步任务队列,useQueue 可以帮助你按顺序处理每个任务,避免任务乱序执行或并发冲突。

js 复制代码
import { useState, useCallback } from 'react';

function useQueue() {
  const [queue, setQueue] = useState([]);

  // 入队
  const enqueue = useCallback((item) => {
    setQueue((prevQueue) => [...prevQueue, item]);
  }, []);

  // 出队
  const dequeue = useCallback(() => {
    setQueue((prevQueue) => prevQueue.slice(1));
  }, []);

  return {
    queue,
    enqueue,
    dequeue
  };
}

export default useQueue;

应用场景

  • 任务排队:例如,在异步请求的场景下,任务需要按顺序执行,可以利用队列来确保任务按预定顺序逐个执行。
  • 消息队列:在应用中,队列可能被用来处理用户输入、消息发送等按顺序进行的操作。

usePolling

usePolling 是一个自定义的 React hook,用于实现 轮询 功能。轮询是一种定期检查某些数据或状态是否发生变化的机制,通常用于获取远程服务器的数据或检查某些后台任务的状态。usePolling hook 使得在 React 组件中实现轮询变得更加简洁和可维护。

轮询的典型应用:

  • 实时数据更新:例如,你的应用需要定期从服务器获取新的数据(例如实时股票价格、新闻更新等)。
  • 任务状态检查:例如,用户在后台提交了一个任务,需要定期检查任务的状态是否已完成。
  • 通知系统:例如,定期检查是否有新的通知或消息。

usePolling 通过定时请求某个函数(比如通过 API 请求、WebSocket 等),并根据需求设置轮询的间隔,直到满足停止条件。

以下是一个简单的 usePolling 实现示例:

js 复制代码
function usePolling(callback, interval, shouldStopPolling) {
  const [isPolling, setIsPolling] = useState(false);

  const cbRef = useRef(callback);
  cbRef.current = callback;

  useEffect(() => {
    if (!shouldStopPolling) {
      return;
    }

    const intervalId = setInterval(() => {
      cbRef.current?.();
    }, interval);

    setIsPolling(true);

    return () => {
      clearInterval(intervalId);
      setIsPolling(false);
    };
  }, [interval, shouldStopPolling]);

  return isPolling;
}

useFullscreen

useFullscreen 是一个自定义的 React hook,用于让一个元素进入或退出全屏模式。它简化了在 React 中处理全屏功能的代码,允许你以更简洁的方式控制页面或元素的全屏显示。

以下是一个简单的 useFullscreen 实现示例:

js 复制代码
import { useState, useRef, useCallback } from "react";

function useFullscreen() {
  const [isFullscreen, setIsFullscreen] = useState(false);
  const elementRef = useRef(null);

  const enterFullscreen = useCallback(() => {
    if (elementRef.current) {
      const element = elementRef.current;
      if (element.requestFullscreen) {
        element.requestFullscreen();
      } else if (element.mozRequestFullScreen) {
        element.mozRequestFullScreen(); // Firefox
      } else if (element.webkitRequestFullscreen) {
        element.webkitRequestFullscreen(); // Chrome, Safari, and Opera
      } else if (element.msRequestFullscreen) {
        element.msRequestFullscreen(); // IE/Edge
      }
      setIsFullscreen(true);
    }
  }, []);

  const exitFullscreen = useCallback(() => {
    if (document.exitFullscreen) {
      document.exitFullscreen();
    } else if (document.mozCancelFullScreen) {
      document.mozCancelFullScreen(); // Firefox
    } else if (document.webkitExitFullscreen) {
      document.webkitExitFullscreen(); // Chrome, Safari, and Opera
    } else if (document.msExitFullscreen) {
      document.msExitFullscreen(); // IE/Edge
    }
    setIsFullscreen(false);
  }, []);

  return { elementRef, isFullscreen, enterFullscreen, exitFullscreen };
}

export default useFullscreen;

useNotification

useNotification 是一个自定义的 React hook,用于简化应用中 浏览器通知(Web Notifications)的管理。它可以帮助你向用户显示桌面通知,而不需要手动处理通知 API 的底层细节。

以下是一个简单的 useNotification 实现示例:

js 复制代码
const useNotification = (title, opts) => {
  const [isSupportedNotification, setIsSupportedNotification] = useState(false);

  const timeout = typeof opts.timeout === "number" ? opts.timeout : 5000;

  let notification = useRef(null);

  let timer;

  const onNotify = () => {
    if (isSupportedNotification) {
      Notification.requestPermission().then(function (permission) {
        if (permission === "granted") {
          console.log("User granted notification permission");

          if (timer) {
            clearTimeout(timer);
          }

          if (notification.current) {
            notification.current.close();
          }

          notification.current = new Notification(title, opts);

          if (typeof opts?.onClick === "function") {
            notification.current.addEventListener("click", (event) => {
              event.preventDefault();
              opts?.onClick?.();
              notification.current.close();
            });
          }

          if (typeof opts?.onClose === "function") {
            notification.current.onclose = opts?.onClose;
          }

          if (typeof opts?.onError === "function") {
            notification.current.onerror = opts?.onError;
          }

          if (typeof opts?.onShow === "function") {
            notification.current.onshow = opts?.onShow;
          }

          timer = setTimeout(function () {
            notification.current?.close();
          }, timeout);
        } else {
          console.log("User denied notification permission");
        }
      });
    }
  };

  const onClose = () => {
    notification.current?.close();
  };

  useEffect(() => {
    setIsSupportedNotification("Notification" in window);
  }, []);

  return {
    onNotify,
    onClose,
  };
};

useAudio

useAudio 是一个自定义的 React hook,用于简化音频播放的管理。它允许你轻松地在 React 组件中控制音频的播放、暂停、音量调节、进度控制等操作。

以下是一个简单的 useAudio 实现示例:

js 复制代码
import { useState, useEffect, useRef } from 'react';

function useAudio(url) {
  const audioRef = useRef(null);
  const [isPlaying, setIsPlaying] = useState(false);
  const [currentTime, setCurrentTime] = useState(0);
  const [duration, setDuration] = useState(0);

  // 当音频 URL 改变时,重新加载音频
  useEffect(() => {
    if (audioRef.current) {
      audioRef.current.src = url;
    }
  }, [url]);

  // 处理音频播放和暂停
  const togglePlay = () => {
    if (isPlaying) {
      audioRef.current.pause();
    } else {
      audioRef.current.play();
    }
    setIsPlaying(!isPlaying);
  };

  // 更新当前播放时间和音频总时长
  const updateTime = () => {
    if (audioRef.current) {
      setCurrentTime(audioRef.current.currentTime);
      setDuration(audioRef.current.duration);
    }
  };

  // 音频播放时的事件监听
  useEffect(() => {
    const audio = audioRef.current;

    if (audio) {
      audio.addEventListener('timeupdate', updateTime);
      audio.addEventListener('ended', () => setIsPlaying(false)); // 播放结束时自动暂停

      return () => {
        audio.removeEventListener('timeupdate', updateTime);
      };
    }
  }, []);

  // 返回给组件的 API
  return {
    audioRef,
    isPlaying,
    currentTime,
    duration,
    togglePlay,
  };
}

export default useAudio;

useLogger

useLogger 是一个自定义的 React hook,用于 日志记录调试,帮助开发者在应用的不同阶段输出调试信息。它的主要作用是让你能够轻松地在 React 组件中打印日志,便于开发、调试和跟踪应用的状态变化或生命周期事件。

useLogger 的常见应用:

  • 开发过程中的调试:记录组件的渲染过程、状态变化等信息。
  • 错误追踪:帮助开发者追踪应用中的错误或异常情况。
  • 性能分析:记录性能相关的数据,比如渲染时间、API 请求等。
  • API 请求日志:自动记录每次发起的 API 请求和响应,便于调试和优化。

通过使用 useLogger,你可以更加高效地跟踪应用中的事件和状态,尤其在开发阶段非常有用。

以下是一个简单的 useLogger 实现示例:

js 复制代码
function useLogger(componentName, state) {
  useEffect(() => {
    console.log(`[${componentName}] Mounted`);
    return () => {
      console.log(`[${componentName}] Unmounted`);
    };
  }, [componentName]);

  useEffect(() => {
    console.log(`[${componentName}] State updated:`, state);
  }, [componentName, state]);
}

useScroll

useScroll 是一个自定义的 React Hook,用于监听和处理滚动事件,特别是用于获取滚动位置(如页面或容器的滚动偏移量)。它可以帮助你在页面或元素滚动时执行特定操作,比如实现滚动加载、滚动动画、动态变化等。

在 React 中,通常通过 window.scrollYdocument.documentElement.scrollTop 来获取滚动位置,而 useScroll 这种自定义 Hook 将这些操作封装起来,提供一个更简洁和 React 风格的方式来处理滚动事件。

通常,useScroll 用于实现一些基于滚动的交互效果,比如:

  • 懒加载:当页面滚动到一定位置时,加载更多内容。
  • 滚动动画:根据滚动位置动态更新界面上的动画效果。
  • 固定导航:滚动到一定位置时,固定顶部或底部的导航栏。

通过 useScroll,你可以避免频繁的手动设置事件监听器,也能方便地在函数组件中处理滚动逻辑。

以下是一个简单的 useScroll 实现示例:

js 复制代码
import { useState, useEffect } from 'react';

function useScroll(target = window) {
  const [scrollPosition, setScrollPosition] = useState({
    x: 0,
    y: 0,
  });

  useEffect(() => {
    const handleScroll = () => {
      if (target === window) {
        setScrollPosition({
          x: window.scrollX,
          y: window.scrollY,
        });
      } else {
        setScrollPosition({
          x: target.scrollLeft,
          y: target.scrollTop,
        });
      }
    };

    target.addEventListener('scroll', handleScroll);

    // 清理事件监听器
    return () => {
      target.removeEventListener('scroll', handleScroll);
    };
  }, [target]);

  return scrollPosition;
}

useMouse

useMouse 是一个自定义的 React Hook,用于获取并监听鼠标的位置。它能够实时追踪鼠标的坐标(如 xy),并在鼠标移动时更新状态。useMouse 常用于实现需要根据鼠标位置进行交互的功能,比如自定义的鼠标指针、拖拽、悬浮效果等。

这个 Hook 可以用于:

  • 鼠标位置显示:实时显示鼠标在页面或某个元素内的坐标。
  • 自定义鼠标指针:根据鼠标的位置改变页面元素的样式或触发动画。
  • 拖拽交互:根据鼠标的位置移动元素,创建拖拽效果。
  • 鼠标悬停效果:在鼠标悬停在某个区域时触发动画或交互。

以下是一个简单的 useMouse 实现示例:

js 复制代码
function useMouse() {
  const [x, setX] = useState(0);
  const [y, setY] = useState(0);

  useEffect(() => {
    const updateMouse = (e) => {
      setX(e.clientX);
      setY(e.clientY);
    };

    window.addEventListener("mousemove", updateMouse);

    return () => {
      window.removeEventListener("mousemove", updateMouse);
    };
  }, []);

  return { x, y };
}

useClipboard

useClipboard 是一个自定义的 React hook,通常用于 复制到剪贴板 的操作。它使得你可以轻松地将文本或数据复制到用户的剪贴板,并且通常会返回一些有用的状态(如复制是否成功、是否正在复制等),以便你可以在界面上给用户反馈。

为什么需要 useClipboard

在现代 Web 应用中,经常会遇到需要将文本、链接或其他数据复制到剪贴板的场景,比如:

  • 用户点击一个按钮将某些信息复制到剪贴板。
  • 实现"复制链接"功能,让用户轻松分享链接。
  • 在表单中让用户复制某些已填好的内容。

通过 useClipboard,你可以轻松实现这些功能,而不需要直接操作底层的 API。

以下是一个简单的 useClipboard 实现示例:

js 复制代码
function useClipboard() {
  const [isCopying, setIsCopying] = React.useState(false);

  const canUseClipboardApi =
    typeof navigator !== "undefined" &&
    navigator.clipboard &&
    navigator.clipboard.writeText;

  const toClipboard = async (text) => {
    try {
      setIsCopying(true);

      if (canUseClipboardApi) {
        await navigator.clipboard.writeText(text);
        console.log("Copied to clipboard via Clipboard API");
      } else {
        const textarea = document.createElement("textarea");
        textarea.value = text;
        document.body.appendChild(textarea);

        textarea.select();
        const successful = document.execCommand("copy");
        document.body.removeChild(textarea);

        if (successful) {
          console.log("Copied to clipboard via execCommand");
        } else {
          throw new Error("Failed to copy via execCommand");
        }
      }
    } catch (e) {
      console.error("Failed to copy text:", e);
    } finally {
      setIsCopying(false);
    }
  };

  return {
    toClipboard,
    isCopying,
  };
}

useTimeAgo

useTimeAgo 是一个自定义的 React hook,通常用于 格式化和显示时间,将给定的时间转换为类似"x分钟前"、"x小时前"或"x天前"的相对时间表示。这个 hook 是一种方便的方式,用于展示与当前时间相比的时间差,通常用于展示动态内容的发布时间、最后一次更新的时间等。

很多时候,尤其是在社交媒体、评论区、消息列表等应用中,我们希望以相对时间的形式展示日期/时间,而不是直接显示一个具体的日期(例如 "2023-01-01")。相对时间如 "2小时前"、"3天前" 等,对于用户来说更具可读性和直观性。

useTimeAgo 就是为了简化这一过程,它会根据当前时间和给定的时间戳,计算出相对时间并以友好的方式展示。

以下是一个简单的 useTimeAgo 实现示例:

js 复制代码
function useTimeAgo(timestamp, options = {}) {
  // 存储最终的时间字符串
  const [timeAgo, setTimeAgo] = useState("");

  // 默认值
  const interval = options.interval ?? 60000; // 默认每分钟更新一次
  const locale = options.locale ?? "en"; // 默认语言为英文

  // 计算相对时间的函数
  const calculateTimeAgo = useCallback(() => {
    // 创建一个 RelativeTimeFormat 实例
    const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });

    const now = Date.now();
    const diffInMs = now - new Date(timestamp).getTime();

    // 转换为秒、分钟、小时等
    const diffInSeconds = Math.floor(diffInMs / 1000);
    const diffInMinutes = Math.floor(diffInSeconds / 60);
    const diffInHours = Math.floor(diffInMinutes / 60);
    const diffInDays = Math.floor(diffInHours / 24);
    const diffInWeeks = Math.floor(diffInDays / 7);
    const diffInMonths = Math.floor(diffInDays / 30);
    const diffInYears = Math.floor(diffInDays / 365);

    // 根据不同的时间差,选择合适的单位和返回相应的字符串
    if (diffInSeconds < 60) {
      setTimeAgo(rtf.format(-diffInSeconds, "second"));
    } else if (diffInMinutes < 60) {
      setTimeAgo(rtf.format(-diffInMinutes, "minute"));
    } else if (diffInHours < 24) {
      setTimeAgo(rtf.format(-diffInHours, "hour"));
    } else if (diffInDays < 7) {
      setTimeAgo(rtf.format(-diffInDays, "day"));
    } else if (diffInWeeks < 5) {
      setTimeAgo(rtf.format(-diffInWeeks, "week"));
    } else if (diffInMonths < 12) {
      setTimeAgo(rtf.format(-diffInMonths, "month"));
    } else {
      setTimeAgo(rtf.format(-diffInYears, "year"));
    }
  }, [locale, timestamp]);

  useEffect(() => {
    // 初始化时计算一次时间
    calculateTimeAgo();
  }, []);

  // 每隔指定时间间隔更新一次相对时间
  useEffect(() => {
    // 启动定时器:每隔指定的时间间隔重新计算一次相对时间
    const intervalId = setInterval(calculateTimeAgo, interval);

    // 清理定时器:组件卸载时清理定时器
    return () => {
      clearInterval(intervalId);
    };
  }, [timestamp, interval, locale, calculateTimeAgo]); // 依赖项包括时间戳、时间间隔和语言设置

  // 返回更新后的时间字符串
  return timeAgo;
}

useIsFirstRender

useIsFirstRender 不是 React 的官方 hook,而是一些开发者或者库中定义的自定义 hook。它的目的是检测组件是否为首次渲染。

通常来说,它的实现原理是利用 React 的 useRef 来存储一个状态,判断组件是否已经渲染过一次。如果是首次渲染,它会返回 true,否则返回 false

以下是一个简单的 useIsFirstRender 实现示例:

js 复制代码
const useIsFirstRender = () => {
  const isFirstRender = useRef(true);

  useEffect(() => {
   // 在第一次渲染完成后,设置为false
    isFirstRender.current = false; 
  }, []);

  return isFirstRender.current;
};
相关推荐
yqcoder3 分钟前
Commander 一款命令行自定义命令依赖
前端·javascript·arcgis·node.js
前端Hardy19 分钟前
HTML&CSS :下雪了
前端·javascript·css·html·交互
醉の虾26 分钟前
VUE3 使用路由守卫函数实现类型服务器端中间件效果
前端·vue.js·中间件
码上飞扬1 小时前
Vue 3 30天精进之旅:Day 05 - 事件处理
前端·javascript·vue.js
火烧屁屁啦2 小时前
【JavaEE进阶】应用分层
java·前端·java-ee
程序员小寒2 小时前
由于请求的竞态问题,前端仔喜提了一个bug
前端·javascript·bug
赵不困888(合作私信)3 小时前
npx和npm 和pnpm的区别
前端·npm·node.js
很酷的站长4 小时前
一个简单的自适应html5导航模板
前端·css·css3
python算法(魔法师版)6 小时前
React应用深度优化与调试实战指南
开发语言·前端·javascript·react.js·ecmascript
阿芯爱编程9 小时前
vue3 vue2区别
前端·javascript·vue.js