usehooks原理解析

78 阅读2分钟

一. usehooks介绍

usehooksReact工具库,封装了很多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.clipboardwriteText方法,如果不支持则回退使用documentexecCommand方法。

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目标节点,使用用法参考文档

核心原理是监听目标节点的mouseentermouseleave两个事件。

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

作用是监听鼠标长按事件,使用用法参考文档

核心原理是装饰onMouseDownonMouseUponMouseLeave等方法,注入额外处理逻辑。

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实现相关功能,提升开发效率。