自定义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