为什么使用hooks
hooks 向后兼容,自由可选,拥抱函数。
hook 在不需要修改组件的情况下复用状态逻辑。
render props、高阶组建、providers、consumers等抽象层组成的组建,容易形成嵌套地狱
hook 将组件中 相互关联反复 的部分 细拆更小的粒度函数。
传统组件副作用、状态逻辑充斥繁多,难以理解,容易产生bug,颗粒化也困难。
常用的自定义hook
1、useDidMount(类似 class componentDidmount)
import { useEffect } from 'react';
const useDidMount = (fn:()=>void) => {
useEffect(()=> {
fn && fn();
}, [fn])
}
export default useDidMount;
2、useUnmount(class componentWillUnmount)
import { useEffect } from 'react';
const useUnmount = (fn:()=>void) => {
// 这里实质return 执行了一个箭头函数
useEffect(()=>()=>{
fn && fn();
}, [fn]);
}
export default useUnmount;
3、useUpdate(class forceUpdate)
// state 没有必要更新,但是需要组件重新渲染做一些无意义的更新
import { useCallBack, useState } from 'react';
const useUpdate = () => {
const [, setState] = useState({});
return useCallback(()=>setState({}), []);
}
export default useUpdate;
4、usePrevious(这个面试的话一般都会问一下)
import { useRef } from 'react';
// 返回前一此render 的 value
// undefined first render
// state next render
function usePrevious(state) {
//one:
// const prevRef = useRef();
// const curRef = useRef();
// prevRef.current = curRef.current;
// curRef.current = state
two:
const prev = useRef();
useEffect(()=> {
prev.current = state;
});
return prevRef.current;
}
export default usePrervious;
5、useTimeout(使用过的时候不用考虑移除定时器)
import { useEffect } from 'react';
function useTimeout(fn, delay) {
useEffect(()=> {
const timer = setTimeout(() => {
fn && fn();
});
return () => {
clearTimeout(timer); // 移除定时器
}
}, [delay, fn]);
}
6、useInterval
import { useEffect } from 'react';
function useInterval(fn, dealy) {
useEffect(()=>{
const timer = setInterval(()=>{
fn && fn();
})
return () => {
clearInterval(timer);
}
}, [delay, fn])
}
export default useInterval;
7、实现自定义的useDebounce(防抖)
import { useCallback, useRef } from 'react';
const useDebounce = (fn, delay, argu) => {
const timer = useRef();
return useCallback((...args) => {
if (timer.current) {
clearTimeout(timer.current);
}
timer.current = setTimeout(()=>{
fn && fn(...args);
}, delay);
}, [delay, fn, ...argu]);
}
export default useDebounce;
8、useThrottle
import { useCallback, useRef} from 'react';
const useThrottle = (fn, delay, argu) => {
const timer = useRef();
return useCallback((...args) => {
if(timer.current) return;
timer.current = setTimeout(() => {
fn && fn();
timer.current = null;
}, delay);
}, [delay, fn, ...argu]);
}
export default useThrottle;
9、图片懒加载useLazyLoad
import { useCallback, useEffect, useState } from 'react';
import useThrottle from './useThrottle';
const useLazyLoad = ({
domList,
imgList,
throttle
}) => {
const loadImg = useThrottle(()=> {
domList.forEach((el, i) => {
if(!el || imgList[i].loaded) return;
if(el.offsetTop < document.documentElement.scrollTop + document.documentElement.clientHeight) {
el.src = el.dataset.src;
//el.src = el.getAttribute("data-src");
el.loaded = true;
}
})
}, throttle, [domList, imgList]);
const interObserver = useCallBack(()=> {
if (IntersectionObserver) {
let lazyImageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry, index) => {
let lazyImage = entry.target;
// 如果元素可见
if (entry.intersectionRatio > 0 && !imgList[index].loaded) {
lazyImage.src = lazyImage.getAttribute("data-src");
lazyImageObserver.unobserve(lazyImage)
}
})
})
for (let i = 0; i < domList.length; i++) {
lazyImageObserver.observe(domList[i]);
}
}
}, [domList, imgList]);
const beginLoad = useCallBack(() => loadImg(), []);
useEffect(() => {
if(IntersectionObserver) {
document.addEventListener('scroll', beginLoad);
} else {
interObserver()
}
return () => {
!IntersectionObserver && document.removeEventListener('scroll', beginLoad)
}
}, [])
}
export default useLazyLoad;
demo:
const LazyLoadDemo:React.FC = () => {
const domRef = useRef([]);
const imgList = Array.from({length: 30 }).map((v, i) => ({
id: i,
src,
loadingSrc,
loaded: false
}))
useLazyLoad({domList: domRef.current, imgList, throttle: 200 });
return (
<div className = "wrapper">
{
imgList.map((v, i) => (
<img
key={v.id}
ref={()=>(domRef.current[i] = el)}
src = {v.loadingSrc}
data-src={v.src}
alt=""
className="item"
/>
))
}
</div>
)
}
10、useIsMounted
import { useCallback, useEffect, useRef } from 'react';
export function useIsMounted(initialValue = false): () => boolean {
const isMounted = useRef(initialValue);
const get = useCallback(()=> isMounted.current, []);
useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, [])
return get;
}
11、useSafeState
// 对于useState 可能组件卸载的时候还在设置;
import { useCallback, useState } from 'react';
export function useSafeState(initialState) {
const [state, setState] = useState(initialState);
const isMounted = useIsMounted(true);
return [state, useCallback((value) => {
if (isMounted()) setState(value);
})]
};
12、useIntersectionObserver
import { useEffect } from 'react';
import { useSafeState } from './useSafeState';
const DEFAULT_THRESHOLD = [0];
const DEFAUT_ROOT_MARGIN = '0px';
const observers = new Map();
const getObserverEntry = (options) => {
const root = options.root ?? document;
let rootObservers = observers.get(root);
if (!rootObservers) {
rootObservers = new Map();
observers.set(root, rootObservers);
}
const ...待开发中
}
export function useIntersectionObserver(
target,
{
threshold = DEFAULT_THRESHOLD
root: r,
rootMargin = DEFAUT_ROOT_MARGIN
}) {
const [state, setState] = useSafeState();
useEffect(() => {
const tgt = target && 'current' in target ? target.current : target;
if (!tgt) return;
let subscribed = true;
const observerEntry = getObserverEntry({
root: r && 'current' in r ? r.current : r,
rootMargin,
threshold,
});
const handler = (entry) => {
if(subscribed) {
setState(entry)
}
}
observerEntry.observer(tgt, handler);
return () => {
subscribed = false;
observerEntry.unobserver(tgt, hander);
}
}, [target, r, rootMargin, ...threshold]);
}