一. usehooks
介绍
usehooks
是React
工具库,封装了很多Hook
工具方法。具体用法参考文档
二. usehooks
原理
剖析常用Hook
方法实现原理
2.1 useClickAway
作用是检测点击区域是否在指定容器之外,使用用法参考文档。
核心原理是通过DOM
节点的contains
方法判断触发事件节点是否包含在容器内,如果不是则触发事件回调。
import React from 'react'
export function useClickAway(cb) {
const ref = React.useRef(null)
const refCb = React.useRef(cb)
React.useLayoutEffect(() => {
refCb.current = cb
})
React.useEffect(() => {
const handler = e => {
const element = ref.current
if (element && !element.contains(e.target)) {
refCb.current(e)
}
}
document.addEventListener('mousedown', handler)
document.addEventListener('touchstart', handler)
return () => {
document.removeEventListener('mousedown', handler)
document.removeEventListener('touchstart', handler)
}
}, [])
return ref
}
2.2 useCopyToClipboard
作用是复制内容到剪切板,使用用法参考文档。
核心原理是调用navigator.clipboard
的writeText
方法,如果不支持则回退使用document
的execCommand
方法。
import React from 'react'
export function useCopyToClipboard() {
const [state, setState] = React.useState(null)
const copyToClipboard = React.useCallback(value => {
const handleCopy = async () => {
if (navigator?.clipboard?.writeText) {
await navigator.clipboard.writeText(value)
} else {
const tempTextArea = document.createElement('textarea')
tempTextArea.value = value
document.body.appendChild(tempTextArea)
tempTextArea.select()
document.execCommand('copy')
document.body.removeChild(tempTextArea)
}
setState(value)
}
handleCopy()
}, [])
return [state, copyToClipboard]
}
2.3 useDebounce
作用是监听value
值变更,当指定时间内value
值没有变化时则修改debouncedValue
,用于输入框搜索场景,使用用法参考文档。
核心原理是监听value
值的变更,添加setTimeout
事件,当指定时间内value
值没有变化则触发setTimeout
事件回调。
import React from 'react'
export function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = React.useState(value)
React.useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value)
}, delay)
return () => {
clearTimeout(handler)
}
}, [value, delay])
return debouncedValue
}
2.4 useHover
作用是追踪鼠标是否hover
目标节点,使用用法参考文档。
核心原理是监听目标节点的mouseenter
和mouseleave
两个事件。
import React from 'react'
export function useHover() {
const [hovering, setHovering] = React.useState(false)
const previousNode = React.useRef(null)
const handleMouseEnter = React.useCallback(() => {
setHovering(true)
}, [])
const handleMouseLeave = React.useCallback(() => {
setHovering(false)
}, [])
const customRef = React.useCallback(
node => {
if (previousNode.current?.nodeType === Node.ELEMENT_NODE) {
previousNode.current.removeEventListener('mouseenter', handleMouseEnter)
previousNode.current.removeEventListener('mouseleave', handleMouseLeave)
}
if (node?.nodeType === Node.ELEMENT_NODE) {
node.addEventListener('mouseenter', handleMouseEnter)
node.addEventListener('mouseleave', handleMouseLeave)
}
previousNode.current = node
},
[handleMouseEnter, handleMouseLeave],
)
return [customRef, hovering]
}
2.5 useIntersectionObserver
作用是追踪目标节点是否在可视窗口内,使用用法参考文档。
核心原理是使用浏览器提供的IntersectionObserver
对象。
import React from 'react'
export function useIntersectionObserver(options = {}) {
const { threshold = 1, root = null, rootMargin = '0px' } = options
const [entry, setEntry] = React.useState(null)
const previousObserver = React.useRef(null)
const customRef = React.useCallback(
node => {
if (previousObserver.current) {
previousObserver.current.disconnect()
previousObserver.current = null
}
if (node?.nodeType === Node.ELEMENT_NODE) {
const observer = new IntersectionObserver(
([entry]) => {
setEntry(entry)
},
{ threshold, root, rootMargin },
)
observer.observe(node)
previousObserver.current = observer
}
},
[threshold, root, rootMargin],
)
return [customRef, entry]
}
2.6 useLongPress
作用是监听鼠标长按事件,使用用法参考文档。
核心原理是装饰onMouseDown
、onMouseUp
和onMouseLeave
等方法,注入额外处理逻辑。
import React from 'react'
function isTouchEvent({ nativeEvent }) {
return window.TouchEvent
? nativeEvent instanceof TouchEvent
: 'touches' in nativeEvent
}
function isMouseEvent(event) {
return event.nativeEvent instanceof MouseEvent
}
export function useLongPress(callback, options = {}) {
const { threshold = 400, onStart, onFinish, onCancel } = options
const isLongPressActive = React.useRef(false)
const isPressed = React.useRef(false)
const timerId = React.useRef()
return React.useMemo(() => {
if (typeof callback !== 'function') return
const start = event => {
if (!isMouseEvent(event) && !isTouchEvent(event)) return
if (onStart) onStart(event)
isPressed.current = true
timerId.current = setTimeout(() => {
callback(event)
isLongPressActive.current = true
}, threshold)
}
const cancel = event => {
if (!isMouseEvent(event) && !isTouchEvent(event)) return
if (isLongPressActive.current) {
onFinish?.(event)
} else if (isPressed.current) {
onCancel?.(event)
}
isLongPressActive.current = false
isPressed.current = false
if (timerId.current) clearTimeout(timerId.current)
}
const mouseHandlers = {
onMouseDown: start,
onMouseUp: cancel,
onMouseLeave: cancel,
}
const touchHandlers = {
onTouchStart: start,
onTouchEnd: cancel,
}
return {
...mouseHandlers,
...touchHandlers,
}
}, [callback, threshold, onStart, onFinish, onCancel])
}
三. 总结
本文介绍了几个实用的Hook
方法原理,如果想了解更多可以查阅代码仓库。在日常开发中可以考虑使用usehooks
实现相关功能,提升开发效率。