Skip to content

自定义hooks

自定义的useState,支持类似class组件setState方法

熟悉react的朋友都知道,我们使用class组件更新状态时,setState会支持两个参数,一个是更新后的state或者回调式更新的state,另一个参数是更新后的回调函数,如下面的用法:

js
this.setState({num: 1}, () => {
    console.log('updated')
})

但是hooks函数的useState第二个参数回调支持类似class组件的setState的第一个参数的用法,并不支持第二个参数回调,但是很多业务场景中我们又希望hooks组件能支持更新后的回调这一方法,那该怎么办呢?

其实问题也很简单,我们只要对hooks原理和api非常清楚的话,就可以通过自定义hooks来实现,这里我们借助上面提到的useRef和useEffect配合useState来实现这一功能。

注:react hooks的useState一定要放到函数组件的最顶层,不能写在ifelse等条件语句当中,来确保hooks的执行顺序一致,因为useState底层采用链表结构实现,有严格的顺序之分。

javascript
import { useEffect, useRef, useState } from 'react'
 
const useXState = (initState) => {
    const [state, setState] = useState(initState)
    let isUpdate = useRef()
    const setXState = (state, cb) => {
      setState(prev => {
        isUpdate.current = cb
        return typeof state === 'function' ? state(prev) : state
      })
    }
    useEffect(() => {
      if(isUpdate.current) {
        isUpdate.current()
      }
    })
  
    return [state, setXState]
  }
 
export default useXState

实现防抖hooks

useDebounce 接受三个参数,分别为回调函数,时间间隔以及依赖项数组,它暴露了 cancel API ,主要是用来控制何时停止防抖函数用的。

javascript
import { useEffect, useRef } from 'react'
 
const useDebounce = (fn, ms = 30, deps = []) => {
    let timeout = useRef()
    useEffect(() => {
        if (timeout.current) clearTimeout(timeout.current)
        timeout.current = setTimeout(() => {
            fn()
        }, ms)
    }, deps)
    const cancel = () => {
        clearTimeout(timeout.current)
        timeout = null
    }
  
    return [cancel]
  }
 
export default useDebounce

实现节流hooks

javascript
import { useEffect, useRef, useState } from 'react'
 
const useThrottle = (fn, ms = 30, deps = []) => {
    let previous = useRef(0)
    let [time, setTime] = useState(ms)
    useEffect(() => {
        let now = Date.now();
        if (now - previous.current > time) {
            fn();
            previous.current = now;
        }
    }, deps)
 
    const cancel = () => {
        setTime(0)
    }
  
    return [cancel]
  }
 
export default useThrottle

实现自定义的useScroll

监听一个元素滚动位置

javascript
import { useState, useEffect } from 'react'
 
const useScroll = (scrollRef) => {
  const [pos, setPos] = useState([0,0])
 
  useEffect(() => {
    function handleScroll(e){
      setPos([scrollRef.current.scrollLeft, scrollRef.current.scrollTop])
    }
    scrollRef.current.addEventListener('scroll', handleScroll, false)
    return () => {
      scrollRef.current.removeEventListener('scroll', handleScroll, false)
    }
  }, [])
  
  return pos
}
 
export default useScroll

Released under the MIT License.