笔者总结一些工作中常用的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,用于处理计数器功能(如增加、减少计数)。它封装了对状态的管理和更新,简化了计数器的实现。你可以通过传递初始值和增减步长来使用它,并且它通常会暴露一组操作方法,比如 increment
、decrement
和 reset
等。
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 中,每当组件重新渲染时,所有的函数都会被重新创建,这可能会导致不必要的性能问题,特别是在函数作为依赖传递给 useEffect
、useCallback
或子组件的 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 基本上监听 window
的 resize
事件,确保在窗口大小变化时,组件能够自动更新其状态,并提供最新的窗口尺寸。
以下是一个简单的 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
useDrag
和 useDrop
是 React hook,用于实现 拖拽(drag-and-drop) 功能。这两个 hook 通常与 React DnD(Drag and Drop)库一起使用,允许你在 React 应用中创建交互式的拖拽效果,比如拖动列表项、拖动文件等。
这两个 hook 提供了一种简洁的方式来处理拖拽操作,包括拖动、放置、更新组件状态等。它们通常一起使用:useDrag
用于设置拖动源,useDrop
用于设置放置目标。
useQueue
useQueue
通常是在编程或前端开发中使用的一个自定义 Hook,用于处理队列相关的操作。在 React 或 JavaScript 的一些框架中,它可能被用来管理队列的状态或逻辑。
具体来说,useQueue
可能会执行以下任务:
- 管理队列的状态:存储和管理一个数据结构(如数组或链表),表示当前的队列。
- 操作队列:提供像入队(enqueue)和出队(dequeue)这样的操作函数。
- 处理异步任务 :在异步场景下,
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.scrollY
或 document.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,用于获取并监听鼠标的位置。它能够实时追踪鼠标的坐标(如 x
和 y
),并在鼠标移动时更新状态。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;
};