React常用自定义hooks

288 阅读2分钟

为什么使用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]);

    }