自定义hooks
Jul 27, 2022 ·
3 Min Read
自定义的useState,支持类似class组件setState方法
熟悉react的朋友都知道,我们使用class组件更新状态时,setState会支持两个参数,一个是更新后的state或者回调式更新的state,另一个参数是更新后的回调函数,如下面的用法:
this.setState({num: 1}, () => { console.log('updated')})
但是hooks函数的useState第二个参数回调支持类似class组件的setState的第一个参数的用法,并不支持第二个参数回调,但是很多业务场景中我们又希望hooks组件能支持更新后的回调这一方法,那该怎么办呢?
其实问题也很简单,我们只要对hooks原理和api非常清楚的话,就可以通过自定义hooks来实现,这里我们借助上面提到的useRef和useEffect配合useState来实现这一功能。
注:react hooks的useState一定要放到函数组件的最顶层,不能写在ifelse等条件语句当中,来确保hooks的执行顺序一致,因为useState底层采用链表结构实现,有严格的顺序之分。
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
,主要是用来控制何时停止防抖函数用的。
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
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
监听一个元素滚动位置
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
Last edited Feb 15