React钩子封装记录 副作用钩子 用于设置导航栏标题。
语法:1 useTitle(title, options) 
参数:
title:{String},标题文案 
options:{Object},参数,可选
restoreOnUnmount:{Boolean}是否退出还原标题 
 
 
 
使用:1 2 3 4 5 6 7 import  {useTitle} from  'react-use' ;const  Demo = ()  =>  {  useTitle('Hello title!' );   return  null ; }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import  { useRef, useEffect } from  'react' ;export  interface UseTitleOptions {  restoreOnUnmount?: boolean; } const  DEFAULT_USE_TITLE_OPTIONS: UseTitleOptions = {  restoreOnUnmount: false , }; function  useTitle (title: string, options: UseTitleOptions = DEFAULT_USE_TITLE_OPTIONS )  {  const  prevTitleRef = useRef(document .title);   document .title = title;   useEffect(()  =>  {     if  (options && options.restoreOnUnmount) {       return  ()  =>  {         document .title = prevTitleRef.current;       };     } else  {       return ;     }   }, []); } export  default  typeof  document  !== 'undefined'  ? useTitle : (_title: string ) =>  {};
 
其中主要是利用document.title来设置页面标题,还原标题则是借助ref来进行旧标题暂存。
用于cookie的处理。
语法:1 const [value, updateCookie, deleteCookie] = useTitle(cookieName) 
参数:
cookieName{String},cookie标识 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import  { useCookie } from  "react-use" ;const  Demo = ()  =>  {  const  [value, updateCookie, deleteCookie] = useCookie("my-cookie" );   const  [counter, setCounter] = useState(1 );   useEffect(()  =>  {     deleteCookie();   }, []);   const  updateCookieHandler = ()  =>  {     updateCookie(`my-awesome-cookie-${counter} ` );     setCounter(c  =>  c + 1 );   };   return  (     <div>       <p>Value: {value}</p>        <button onClick={updateCookieHandler}>Update Cookie</ button>      <br />       <button onClick={deleteCookie}>Delete Cookie</button>      </ div>  ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import  { useState, useCallback } from  'react' ;import  Cookies from  'js-cookie' ;const  useCookie = (  cookieName: string ): [string | null , (newValue: string, options?: Cookies.CookieAttributes) => void , () => void ] => {   const  [value, setValue] = useState<string | null >(( ) =>  Cookies.get(cookieName) || null );   const  updateCookie = useCallback(     (newValue: string, options?: Cookies.CookieAttributes) => {       Cookies.set(cookieName, newValue, options);       setValue(newValue);     },     [cookieName]   );   const  deleteCookie = useCallback(()  =>  {     Cookies.remove(cookieName);     setValue(null );   }, [cookieName]);   return  [value, updateCookie, deleteCookie]; }; export  default  useCookie;
 
1.核心的cookie操作是基于js-cookie 这个封装库 
2.通过useState控制cookie的取值状态 
 
用于localStorage的处理。
语法:1 2 3 4 5 const [value, setValue, remove] = useLocalStorage(key, initialValue, {   raw: false,   serializer: (value: T) => string,   deserializer: (value: string) => T, }); 
参数:
key:{String},storage标识 
initialValue:{any},storage值 
options:参数,可选
raw:{Boolean},是否自定义加工处理。默认false,为JSON.stringify/JSON.parse。 
serializer:{Function},当options.raw为true时生效,设置storage值的加工函数 
deserializer:{Function},当options.raw为true时生效,读取storage值的加工函数 
 
 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 import  { useLocalStorage } from  'react-use' ;const  Demo = ()  =>  {  const  [value, setValue, remove] = useLocalStorage('my-key' , 'foo' );   return  (     <div>       <div>Value: {value}</div>        <button onClick={() => setValue('bar')}>bar</ button>      <button onClick={() => setValue('baz' )}>baz</button>        <button onClick={() => remove()}>Remove</ button>    </div>    ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 import  { useState, useCallback, Dispatch, SetStateAction } from  'react' ;import  { isClient } from  './util' ;type parserOptions<T> =   | {       raw: true ;     }   | {       raw: false ;       serializer: (value: T ) =>  string;       deserializer: (value: string ) =>  T;     }; const  noop = ()  =>  {};const  useLocalStorage = <T > (  key: string,   initialValue?: T,   options?: parserOptions<T> ): [T | undefined, Dispatch<SetStateAction<T | undefined>>, () => void] => {   if (!isClient) {     return [initialValue as T, noop, noop];   }   if (!key) {     throw new Error('useLocalStorage key may not be falsy');   }   const deserializer = options ? (options.raw ? value => value : options.deserializer) : JSON.parse;   const [state, setState] = useState<T | undefined>(() => {     try {       const serializer = options ? (options.raw ? String : options.serializer) : JSON.stringify;       const localStorageValue = localStorage.getItem(key);       if (localStorageValue !== null) {         return deserializer(localStorageValue);       } else {         initialValue && localStorage.setItem(key, serializer(initialValue));         return initialValue;       }     } catch {       // If user is in private mode or has storage restriction       // localStorage can throw. JSON.parse and JSON.stringify       // can throw, too.       return initialValue;     }   });   const set: Dispatch<SetStateAction<T | undefined>> = useCallback(     valOrFunc => {       try {         const newState = typeof valOrFunc === 'function' ? (valOrFunc as Function)(state) : valOrFunc;         if (typeof newState === 'undefined') return;         let value: string;         if (options)           if (options.raw)             if (typeof newState === 'string') value = newState;             else value = JSON.stringify(newState);           else if (options.serializer) value = options.serializer(newState);           else value = JSON.stringify(newState);         else value = JSON.stringify(newState);         localStorage.setItem(key, value);         setState(deserializer(value));       } catch {         // If user is in private mode or has storage restriction         // localStorage can throw. Also JSON.stringify can throw.       }     },     [key, setState]   );   const remove = useCallback(() => {     try {       localStorage.removeItem(key);       setState(undefined);     } catch {       // If user is in private mode or has storage restriction       // localStorage can throw.     }   }, [key, setState]);   return [state, set, remove]; }; export default useLocalStorage; 
 
1.对非浏览器环境(用window变量判断)做了处理,直接返回原值,操作函数为空函数; 
serializer/deserializer处理函数用于hook state调用,不影响localStorage的读写; 
 
 
3.参数raw为false时,默认设置storage操作时会执行JSON.stringify(val)处理,读取storage操作时会执行JSON.parse(val)处理; 
4.参数raw为true时,serializer默认为String,即设置storage操作时执行String(val)处理;deserializer默认为value => value,即读取storage操作时会直接返回; 
 
用于sessionStorage的处理。
语法:1 2 3 const [value, setValue] = useSessionStorage(key, initialValue, {   raw: false, }); 
参数:
key:{String},storage标识 
initialValue:{any},storage值 
options:参数,可选
raw:{Boolean},是否自定义加工处理。默认false,为JSON.stringify/JSON.parse。 
 
 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 import  {useSessionStorage} from  'react-use' ;const  Demo = ()  =>  {  const  [value, setValue] = useSessionStorage('my-key' , 'foo' );   return  (     <div>       <div>Value: {value}</div>        <button onClick={() => setValue('bar')}>bar</ button>      <button onClick={() => setValue('baz' )}>baz</button>      </ div>  ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import  { useEffect, useState } from  'react' ;import  { isClient } from  './util' ;const  useSessionStorage = <T > (key: string, initialValue?: T, raw?: boolean): [T, (value: T) => void] => {  if (!isClient) {     return [initialValue as T, () => {}];   }   const [state, setState] = useState<T>(() => {     try {       const sessionStorageValue = sessionStorage.getItem(key);       if (typeof sessionStorageValue !== 'string') {         sessionStorage.setItem(key, raw ? String(initialValue) : JSON.stringify(initialValue));         return initialValue;       } else {         return raw ? sessionStorageValue : JSON.parse(sessionStorageValue || 'null');       }     } catch {       // If user is in private mode or has storage restriction       // sessionStorage can throw. JSON.parse and JSON.stringify       // cat throw, too.       return initialValue;     }   });   useEffect(() => {     try {       const serializedState = raw ? String(state) : JSON.stringify(state);       sessionStorage.setItem(key, serializedState);     } catch {       // If user is in private mode or has storage restriction       // sessionStorage can throw. Also JSON.stringify can throw.     }   });   return [state, setState]; }; export default useSessionStorage; 
 
1.对非浏览器环境(用window变量判断)做了处理,直接返回原值,操作函数为空函数; 
2.参数raw为false时,默认设置storage操作时会执行JSON.stringify(val)处理,读取storage操作时会执行JSON.parse(val)处理; 
3.sessionStorage的应用场景相对localStorage较小,因此无需发布订阅的控制 
 
控制requestAnimationFrame循环。
语法:1 const [stopLoop, startLoop, isActive] = useRafLoop(callback: FrameRequestCallback, initiallyActive = true); 
参数:
callback:{Function},RAF片段的控制回调函数 
initiallyActive: {boolean},是否已开始就激活循环,默认为true。 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import  * as  React from  'react' ;import  { useRafLoop, useUpdate } from  'react-use' ;const  Demo = ()  =>  {  const  [ticks, setTicks] = React.useState(0 );   const  [lastCall, setLastCall] = React.useState(0 );   const  update = useUpdate();   const  [loopStop, loopStart, isActive] = useRafLoop((time ) =>  {     setTicks(ticks  =>  ticks + 1 );     setLastCall(time);   });   return  (     <div>       <div>RAF triggered: {ticks} (times)</div>        <div>Last high res timestamp: {lastCall}</ div>      <br />       <button onClick={() => {         isActive() ? loopStop() : loopStart();         update();       }}>{isActive() ? 'STOP'  : 'START' }</button>      </ div>  ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import  { useCallback, useEffect, useMemo, useRef } from  'react' ;export  type RafLoopReturns = [()  =>  void , () => void , () => boolean];export  default  function  useRafLoop (callback: FrameRequestCallback, initiallyActive = true ): RafLoopReturns   {  const  raf = useRef<number | null >(null );   const  rafActivity = useRef<boolean>(false );   const  rafCallback = useRef(callback);   rafCallback.current = callback;   const  step = useCallback((time: number ) =>  {     if  (rafActivity.current) {       rafCallback.current(time);       raf.current = requestAnimationFrame(step);     }   }, []);   const  result = useMemo(()  =>  ([     () => {        if  (rafActivity.current) {         rafActivity.current = false ;         raf.current && cancelAnimationFrame(raf.current);       }     },     () => {        if  (!rafActivity.current) {         rafActivity.current = true ;         raf.current = requestAnimationFrame(step);       }     },     (): boolean  =>  rafActivity.current          ] as  RafLoopReturns), []);   useEffect(()  =>  {     if  (initiallyActive) {       result[1 ]();     }     return  result[0 ];        }, []);   return  result; } 
 
1.用ref来缓存raf控制器; 
2.用useMemo()来缓存控制方法; 
3.组件销毁时会取消raf; 
 
抛出异常控制。
语法:1 const dispatchError = useError(); 
参数:无
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import  { useError } from  'react-use' ;const  Demo = ()  =>  {  const  dispatchError = useError();   const  clickHandler = ()  =>  {     dispatchError(new  Error ('Some error!' ));   };   return  <button  onClick ={clickHandler} > Click me to throw</button >  ; }; const  App = ()  =>  (  <ErrorBoundary>     <Demo />   </ErrorBoundary>  ); 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import  { useState, useEffect, useCallback } from  'react' ;const  useError = () : ((err: Error  ) =>  void ) => {  const  [error, setError] = useState<Error  | null >(null );   useEffect(()  =>  {     if  (error) {       throw  error;     }   }, [error]);   const  dispatchError = useCallback((err: Error  ) =>  {     setError(err);   }, []);   return  dispatchError; }; export  default  useError;
 
throw异常。
设置浏览器标题图标。
语法:
参数:
使用:1 2 3 4 5 6 7 import  {useFavicon} from  'react-use' ;const  Demo = ()  =>  {  useFavicon('https://cdn.sstatic.net/Sites/stackoverflow/img/favicon.ico' );   return  null ; }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import  { useEffect } from  'react' ;const  useFavicon = (href: string ) =>  {  useEffect(()  =>  {     const  link: HTMLLinkElement = document .querySelector("link[rel*='icon']" ) || document .createElement('link' );     link.type = 'image/x-icon' ;     link.rel = 'shortcut icon' ;     link.href = href;     document .getElementsByTagName('head' )[0 ].appendChild(link);   }, [href]); }; export  default  useFavicon;
 
简单来说,就是设置属性是icon的link标签的引用地址。
浏览器API兼容状态的信息获取(基于navigator.permissions )
语法:1 const state = usePermission({ name: 'microphone' }); 
参数:
使用:1 2 3 4 5 6 7 8 9 10 11 import  {usePermission} from  'react-use' ;const  Demo = ()  =>  {  const  state = usePermission({ name : 'microphone'  });   return  (     <pre>       {JSON .stringify(state, null , 2 )}     </pre>    ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import  { useEffect, useState } from  'react' ;import  { off, on } from  './util' ;type PermissionDesc =   | PermissionDescriptor   | DevicePermissionDescriptor   | MidiPermissionDescriptor   | PushPermissionDescriptor; type State = PermissionState | '' ; const  noop = ()  =>  {};const  usePermission = (permissionDesc: PermissionDesc): State  =>  {  let  mounted = true ;   let  permissionStatus: PermissionStatus | null  = null ;   const  [state, setState] = useState<State>('' );   const  onChange = ()  =>  {     if  (mounted && permissionStatus) {       setState(permissionStatus.state);     }   };   const  changeState = ()  =>  {     onChange();     on(permissionStatus, 'change' , onChange);   };   useEffect(()  =>  {     navigator.permissions       .query(permissionDesc)       .then(status  =>  {         permissionStatus = status;         changeState();       })       .catch(noop);     return  ()  =>  {       mounted = false ;       permissionStatus && off(permissionStatus, 'change' , onChange);     };   }, []);   return  state; }; export  default  usePermission;
 
核心还是navigator.permissions。不过由于兼容问题,基本不会用。
页面将退出/即将重新加载时触发。
语法:1 useBeforeUnload(enabled, message); 
参数:
enabled:{Boolean},是否启用 
message:{String},退出/将重启加载时的提示文案 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import  {useBeforeUnload} from  'react-use' ;const  Demo = ()  =>  {  const  [dirty, toggleDirty] = useToggle(false );   useBeforeUnload(dirty, 'You have unsaved changes, are you sure?' );   return  (     <div>       {dirty && <p > Try to reload or close tab</p >  }       <button onClick={() => toggleDirty()}>{dirty ? 'Disable'  : 'Enable' }</button>      </ div>  ); }; 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import  {useBeforeUnload} from  'react-use' ;const  Demo = ()  =>  {  const  [dirty, toggleDirty] = useToggle(false );   const  dirtyFn = useCallback(()  =>  {     return  dirty;   }, [dirty]);   useBeforeUnload(dirtyFn, 'You have unsaved changes, are you sure?' );   return  (     <div>       {dirty && <p > Try to reload or close tab</p >  }       <button onClick={() => toggleDirty()}>{dirty ? 'Disable'  : 'Enable' }</button>      </ div>  ); }; 
 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import  { useCallback, useEffect } from  'react' ;const  useBeforeUnload = (enabled: boolean | (( ) =>  boolean) = true , message?: string) => {  const  handler = useCallback(     (event: BeforeUnloadEvent) => {       const  finalEnabled = typeof  enabled === 'function'  ? enabled() : true ;       if  (!finalEnabled) {         return ;       }       event.preventDefault();       if  (message) {         event.returnValue = message;       }       return  message;     },     [enabled, message]   );   useEffect(()  =>  {     if  (!enabled) {       return ;     }     window .addEventListener('beforeunload' , handler);     return  ()  =>  window .removeEventListener('beforeunload' , handler);   }, [enabled, handler]); }; export  default  useBeforeUnload;
 
1.核心基于beforeunload的事件和解除; 
2.enabled默认为true,当传入函数时则获取函数执行结果,闭包的作用。; 
3.考虑了IE的event.returnValue用于事件捕获。 
 
beforeunload事件的兼容情况:
控制区域滚动条是否可以滚动。
语法:1 useLockBodyScroll(locked = true, elementRef); 
参数:
locked:{Boolean},是否锁定滚动,默认为true; 
elementRef:{DOMElement},滚动区域元素 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import  {useLockBodyScroll, useToggle} from  'react-use' ;const  Demo = ()  =>  {  const  [locked, toggleLocked] = useToggle(false )   useLockBodyScroll(locked);   return  (     <div>       <button onClick={() => toggleLocked()}>         {locked ? 'Unlock'  : 'Lock' }       </button>      </ div>  ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 import  { RefObject, useEffect, useRef } from  'react' ;export  function  getClosestBody (el: Element | HTMLElement | HTMLIFrameElement | null ): HTMLElement  | null   {  if  (!el) {     return  null ;   } else  if  (el.tagName === 'BODY' ) {     return  el as  HTMLElement;   } else  if  (el.tagName === 'IFRAME' ) {     const  document  = (el as  HTMLIFrameElement).contentDocument;     return  document  ? document .body : null ;   } else  if  (!(el as  HTMLElement).offsetParent) {     return  null ;   }   return  getClosestBody((el as  HTMLElement).offsetParent!); } function  preventDefault (rawEvent: TouchEvent ): boolean   {  const  e = rawEvent || window .event;      if  (e.touches.length > 1 ) return  true ;   if  (e.preventDefault) e.preventDefault();   return  false ; } export  interface BodyInfoItem {  counter: number;   initialOverflow: CSSStyleDeclaration['overflow' ]; } const  isIosDevice =  typeof  window  !== 'undefined'  &&   window .navigator &&   window .navigator.platform &&   /iP(ad|hone|od)/.test(window .navigator.platform); const  bodies: Map <HTMLElement, BodyInfoItem> = new  Map ();const  doc: Document | undefined  = typeof  document  === 'object'  ? document  : undefined ;let  documentListenerAdded = false ;export  default  !doc  ? function  useLockBodyMock (_locked: boolean = true, _elementRef?: RefObject<HTMLElement> )  {}   : function  useLockBody (locked: boolean = true, elementRef?: RefObject<HTMLElement> )  {       elementRef = elementRef || useRef(doc!.body);       const  lock = body  =>  {         const  bodyInfo = bodies.get(body);         if  (!bodyInfo) {           bodies.set(body, { counter : 1 , initialOverflow : body.style.overflow });           if  (isIosDevice) {             if  (!documentListenerAdded) {               document .addEventListener('touchmove' , preventDefault, { passive : false  });               documentListenerAdded = true ;             }           } else  {             body.style.overflow = 'hidden' ;           }         } else  {           bodies.set(body, { counter : bodyInfo.counter + 1 , initialOverflow : bodyInfo.initialOverflow });         }       };       const  unlock = body  =>  {         const  bodyInfo = bodies.get(body);         if  (bodyInfo) {           if  (bodyInfo.counter === 1 ) {             bodies.delete(body);             if  (isIosDevice) {               body.ontouchmove = null ;               if  (documentListenerAdded) {                 document .removeEventListener('touchmove' , preventDefault);                 documentListenerAdded = false ;               }             } else  {               body.style.overflow = bodyInfo.initialOverflow;             }           } else  {             bodies.set(body, { counter : bodyInfo.counter - 1 , initialOverflow : bodyInfo.initialOverflow });           }         }       };       useEffect(()  =>  {         const  body = getClosestBody(elementRef!.current);         if  (!body) {           return ;         }         if  (locked) {           lock(body);         } else  {           unlock(body);         }       }, [locked, elementRef.current]);              useEffect(()  =>  {         const  body = getClosestBody(elementRef!.current);         if  (!body) {           return ;         }         return  ()  =>  {           unlock(body);         };       }, []);     }; 
 
1.<body>和<iframe>元素会默认本身为滚动元素,其他元素则会向上层递归寻找滚动元素,这也是为何官方会推荐使用<body>或<iframe>作为控制元素的原因,因为它们没有parent; 
2.滚动的锁定控制会根据平台来区分处理,iOS是通过touchmove事件来控制,而其他平台是通过设置元素的overflow样式属性控制; 
 
用户剪切板的拷贝控制。
语法:1 const [{value, error, noUserInteraction}, copyToClipboard] = useCopyToClipboard(); 
参数:无
返回:
dataObj:{Object},返回字段
value:{String},已经被拷贝的值; 
error:{String},是否被捕获异常; 
noUserInteraction:{Boolean},用户交互拷贝的值是否暴露给copy-to-clipboard这个库 
 
 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 const  Demo = ()  =>  {  const  [text, setText] = React.useState('' );   const  [state, copyToClipboard] = useCopyToClipboard();   return  (     <div>       <input value={text} onChange={e => setText(e.target.value)} />       <button type="button"  onClick={() => copyToClipboard(text)}>copy text</button>        {state.error         ? <p>Unable to copy value: {state.error.message}</ p>        : state.value && <p > Copied {state.value}</p >  }     </div>    ) } 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 import  writeText from  'copy-to-clipboard' ;import  { useCallback } from  'react' ;import  useMountedState from  './useMountedState' ;import  useSetState from  './useSetState' ;export  interface CopyToClipboardState {  value?: string;   noUserInteraction: boolean;   error?: Error ; } const  useCopyToClipboard = () : [CopyToClipboardState , (value: string ) =>  void ] => {  const  isMounted = useMountedState();   const  [state, setState] = useSetState<CopyToClipboardState>({     value: undefined ,     error: undefined ,     noUserInteraction: true ,   });   const  copyToClipboard = useCallback(value  =>  {     if  (!isMounted()) {       return ;     }     let  noUserInteraction;     let  normalizedValue;     try  {              if  (typeof  value !== 'string'  && typeof  value !== 'number' ) {         const  error = new  Error (`Cannot copy typeof ${typeof  value}  to clipboard, must be a string` );         if  (process.env.NODE_ENV === 'development' ) console .error(error);         setState({           value,           error,           noUserInteraction: true ,         });         return ;       }              else  if  (value === '' ) {         const  error = new  Error (`Cannot copy empty string to clipboard.` );         if  (process.env.NODE_ENV === 'development' ) console .error(error);         setState({           value,           error,           noUserInteraction: true ,         });         return ;       }       normalizedValue = value.toString();       noUserInteraction = writeText(normalizedValue);       setState({         value: normalizedValue,         error: undefined ,         noUserInteraction,       });     } catch  (error) {       setState({         value: normalizedValue,         error,         noUserInteraction,       });     }   }, []);   return  [state, copyToClipboard]; }; export  default  useCopyToClipboard;
 
2.捕获异常通过try...catch... 
3.useMountedState()也是react-use提供的一个钩子函数,用于获取组件mount状态信息; 
 
防抖。
语法:1 2 3 4 const [     isReady: () => boolean | null,     cancel: () => void, ] = useDebounce(fn: Function, ms: number, deps: DependencyList = []); 
参数:
fn:{Function},回调函数; 
ms:{Number},延迟时间,毫秒; 
deps:{Array},依赖列表,等同于useEffect的第二个参数,可选; 
 
返回:
isReady:()=> boolean|null。当前防抖函数的执行状态。
false:pending 
true:called 
null:cancelled 
 
 
cancel: ()=>void。取消执行 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 const  Demo = ()  =>  {  const  [state, setState] = React.useState('Typing stopped' );   const  [val, setVal] = React.useState('' );   const  [debouncedValue, setDebouncedValue] = React.useState('' );   const  [, cancel] = useDebounce(     () => {       setState('Typing stopped' );       setDebouncedValue(val);     },     2000 ,     [val]   );   return  (     <div>       <input         type="text"          value={val}         placeholder="Debounced input"          onChange={({ currentTarget }) => {           setState('Waiting for typing to stop...' );           setVal(currentTarget.value);         }}       />       <div>{state}</div>        <div>         Debounced value: {debouncedValue}         <button onClick={cancel}>Cancel debounce</ button>      </div>      </ div>  ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 import  { DependencyList, useEffect } from  'react' ;import  useTimeoutFn from  './useTimeoutFn' ;export  type UseDebounceReturn = [()  =>  boolean | null , () => void ];export  default  function  useDebounce (fn: Function, ms: number = 0 , deps: DependencyList = [] ): UseDebounceReturn   {  const  [isReady, cancel, reset] = useTimeoutFn(fn, ms);   useEffect(reset, deps);   return  [isReady, cancel]; } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import  { useCallback, useEffect, useRef } from  'react' ;export  type UseTimeoutFnReturn = [()  =>  boolean | null , () => void , () => void ];export  default  function  useTimeoutFn (fn: Function, ms: number = 0  ): UseTimeoutFnReturn   {  const  ready = useRef<boolean | null >(false );   const  timeout = useRef<ReturnType<typeof  setTimeout>>();   const  callback = useRef(fn);   const  isReady = useCallback(()  =>  ready.current, []);   const  set  = useCallback(() => {     ready.current = false ;     timeout.current && clearTimeout(timeout.current);     timeout.current = setTimeout(()  =>  {       ready.current = true ;       callback.current();     }, ms);   }, [ms]);   const  clear = useCallback(()  =>  {     ready.current = null ;     timeout.current && clearTimeout(timeout.current);   }, []);      useEffect(()  =>  {     callback.current = fn;   }, [fn]);      useEffect(()  =>  {     set ();     return clear;   }, [ms]);   return [isReady, clear, set ]; } 
 
1.定时器单独封装成了一个工具hook:useTimeoutFn; 
2.useTimeoutFn中用useRef来缓存定时器返回的定时器id,避免hook的作用域副作用; 
 
节流。
语法:1 2 useThrottle(value, ms?: number); useThrottleFn(fn, ms, args); 
参数:
value:{any},节流的内容 
ms:{Number},节流延迟时间,单位为毫秒 
args:{Array},依赖列表,等同于useEffect的第二个参数,可选; 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 import  React, { useState } from  'react' ;import  { useThrottle, useThrottleFn } from  'react-use' ;const  Demo = ({value} ) =>  {  const  throttledValue = useThrottle(value);      return  (     <>       <div>Value: {value}</div>        <div>Throttled value: {throttledValue}</ div>    </>    ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import  { useEffect, useRef, useState } from  'react' ;import  useUnmount from  './useUnmount' ;const  useThrottle = <T > (value: T, ms: number = 200) => {  const [state, setState] = useState<T>(value);   const timeout = useRef<ReturnType<typeof setTimeout>>();   const nextValue = useRef(null) as any;   const hasNextValue = useRef(0) as any;   useEffect(() => {     if (!timeout.current) {       setState(value);       const timeoutCallback = () => {         if (hasNextValue.current) {           hasNextValue.current = false;           setState(nextValue.current);           timeout.current = setTimeout(timeoutCallback, ms);         } else {           timeout.current = undefined;         }       };       timeout.current = setTimeout(timeoutCallback, ms);     } else {       nextValue.current = value;       hasNextValue.current = true;     }   }, [value]);   useUnmount(() => {     timeout.current && clearTimeout(timeout.current);   });   return state; }; export default useThrottle; 
 
useUnmount主要实现了组件即将销毁时的回调,在useThrottleFn中主要作用是触发组件销毁时的定时器取消; 
useThrottle较为简单,用useEffect来控制节流触发,以value参数作为控制变量。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import  { useEffect, useRef, useState } from  'react' ;import  useUnmount from  './useUnmount' ;const  useThrottleFn = <T,  U  extends  any []> (fn: (...args: U) => T, ms: number = 200, args: U) => {  const [state, setState] = useState<T | null>(null);   const timeout = useRef<ReturnType<typeof setTimeout>>();   const nextArgs = useRef<U>();   useEffect(() => {     if (!timeout.current) {       setState(fn(...args));       const timeoutCallback = () => {         if (nextArgs.current) {           setState(fn(...nextArgs.current));           nextArgs.current = undefined;           timeout.current = setTimeout(timeoutCallback, ms);         } else {           timeout.current = undefined;         }       };       timeout.current = setTimeout(timeoutCallback, ms);     } else {       nextArgs.current = args;     }   }, args);   useUnmount(() => {     timeout.current && clearTimeout(timeout.current);   });   return state; }; export default useThrottleFn; 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import  { useRef } from  'react' ;import  useEffectOnce from  './useEffectOnce' ;const  useUnmount = (fn: ( ) =>  any): void  =>  {  const  fnRef = useRef(fn);      fnRef.current = fn;   useEffectOnce(()  =>  ()  =>  fnRef.current()); }; export  default  useUnmount;
 
1 2 3 4 5 6 7 8 import  { EffectCallback, useEffect } from  'react' ;const  useEffectOnce = (effect: EffectCallback ) =>  {  useEffect(effect, []); }; export  default  useEffectOnce;
 
useThrottleFn与useThrottle类似,主要控制了函数的触发。 
 
异步方法钩子。
语法:1 const [state, fetch] = useAsyncFn<Result, Args>(fn, deps?: any[], initialState?: AsyncState<Result>); 
参数:
fn:{Function},异步函数 
args:{Array},依赖列表,等同于useEffect的第二个参数,可选; 
initialState:{any},初始值 
 
返回:
state:{Object}
loading:{Boolean},是否处于pending 
error:{Error},是否抛出了异常 
value:{String},返回值 
 
 
fetch:开始执行方法 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import  {useAsyncFn} from  'react-use' ;const  Demo = ({url} ) =>  {  const  [state, fetch] = useAsyncFn(async  () => {     const  response = await  fetch(url);     const  result = await  response.text();     return  result   }, [url]);   return  (     <div>       {state.loading         ? <div > Loading...</div >          : state.error           ? <div > Error: {state.error.message}</div >            : <div > Value: {state.value}</div >        }       <button onClick={() => fetch()}>Start loading</button>      </ div>  ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import  { DependencyList, useCallback, useState, useRef } from  'react' ;import  useMountedState from  './useMountedState' ;import  { FnReturningPromise, PromiseType } from  './util' ;export  type AsyncState<T> =  | {       loading: boolean;       error?: undefined ;       value?: undefined ;     }   | {       loading: true ;       error?: Error  | undefined ;       value?: T;     }   | {       loading: false ;       error: Error ;       value?: undefined ;     }   | {       loading: false ;       error?: undefined ;       value: T;     }; type StateFromFnReturningPromise<T extends FnReturningPromise> = AsyncState<PromiseType<ReturnType<T>>>; export  type AsyncFnReturn<T extends FnReturningPromise = FnReturningPromise> = [StateFromFnReturningPromise<T>, T];export  default  function  useAsyncFn <T  extends  FnReturningPromise >(  fn: T,   deps: DependencyList = [],   initialState: StateFromFnReturningPromise<T> = { loading: false }  ): AsyncFnReturn <T >  {  const  lastCallId = useRef(0 );   const  isMounted = useMountedState();   const  [state, set ] = useState<StateFromFnReturningPromise<T>>(initialState);   const callback = useCallback((...args: Parameters<T>): ReturnType<T> => {     const  callId = ++lastCallId.current;     set (prevState => ({ ...prevState, loading : true  }));     return  fn(...args).then(       value => {         isMounted() && callId === lastCallId.current && set ({ value, loading : false  });         return  value;       },       error => {         isMounted() && callId === lastCallId.current && set ({ error, loading : false  });         return  error;       }     ) as  ReturnType<T>;   }, deps);   return  [state, (callback as  unknown) as  T]; } 
 
从util库中引用的FnReturningPromise, PromiseType只是对类型的定义声明; 
 
异步钩子。
语法:1 const state = useAsync(fn, args?: any[]); 
参数:
fn:{Function},异步函数 
args:{Array},依赖列表,等同于useEffect的第二个参数,可选; 
 
返回:
state:{Object}
loading:{Boolean},是否处于pending 
error:{Error},是否抛出了异常 
value:{String},返回值 
 
 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import  {useAsync} from  'react-use' ;const  Demo = ({url} ) =>  {  const  state = useAsync(async  () => {     const  response = await  fetch(url);     const  result = await  response.text();     return  result   }, [url]);   return  (     <div>       {state.loading         ? <div > Loading...</div >          : state.error           ? <div > Error: {state.error.message}</div >            : <div > Value: {state.value}</div >        }     </div>    ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import  { DependencyList, useEffect } from  'react' ;import  useAsyncFn from  './useAsyncFn' ;import  { FnReturningPromise } from  './util' ;export  { AsyncState, AsyncFnReturn } from  './useAsyncFn' ;export  default  function  useAsync <T  extends  FnReturningPromise >(fn: T, deps: DependencyList = [] )  {  const  [state, callback] = useAsyncFn(fn, deps, {     loading: true ,   });   useEffect(()  =>  {     callback();   }, [callback]);   return  state; } 
 
就是在useAsyncFn之上做了一层封装。
带取消功能的异步钩子。
语法:1 const state = useAsyncRetry(fn, args?: any[]); 
参数:
fn:{Function},异步函数 
args:{Array},依赖列表,等同于useEffect的第二个参数,可选; 
 
返回:
state:{Object}
loading:{Boolean},是否处于pending 
error:{Error},是否抛出了异常 
value:{String},返回值 
retry:{Function},取消方法 
 
 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import  {useAsyncRetry} from  'react-use' ;const  Demo = ({url} ) =>  {  const  state = useAsyncRetry(async  () => {     const  response = await  fetch(url);     const  result = await  response.text();     return  result;   }, [url]);   return  (     <div>       {state.loading         ? <div > Loading...</div >          : state.error           ? <div > Error: {state.error.message}</div >            : <div > Value: {state.value}</div >        }       {!loading && <button  onClick ={()  =>  state.retry()}>Start loading</button >  }     </div>    ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import  { DependencyList, useCallback, useState } from  'react' ;import  useAsync, { AsyncState } from  './useAsync' ;export  type AsyncStateRetry<T> = AsyncState<T> & {  retry(): void ; }; const  useAsyncRetry = <T>(fn: () => Promise<T>, deps: DependencyList = []) => {  const [attempt, setAttempt] = useState<number>(0);   const state = useAsync(fn, [...deps, attempt]);   const stateLoading = state.loading;   const retry = useCallback(() => {     if (stateLoading) {       if (process.env.NODE_ENV === 'development') {         console.log('You are calling useAsyncRetry hook retry() method while loading in progress, this is a no-op.');       }       return;     }     setAttempt(currentAttempt => currentAttempt + 1);   }, [...deps, stateLoading]);   return { ...state, retry }; }; export default useAsyncRetry; 
 
基于useAsync的封装,通过封装回调方法来控制退出方法。
生命周期 只执行一次的effect钩子。
语法:1 useEffectOnce(effect: EffectCallback); 
参数:
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 import  {useEffectOnce} from  'react-use' ;const  Demo = ()  =>  {  useEffectOnce(()  =>  {     console .log('Running effect once on mount' )     return  ()  =>  {       console .log('Running clean-up of effect on unmount' )     }   });   return  null ; }; 
源码及解析 1 2 3 4 5 6 7 import  { EffectCallback, useEffect } from  'react' ;const  useEffectOnce = (effect: EffectCallback ) =>  {	useEffect(effect, []); }; export  default  useEffectOnce;
 
不需要解释。。。
事件处理。
语法:
1 2 useEvent(event, handler) useEvent(event, handler, target, options) 
 
参数:
event:{String},事件名称; 
handler:{function},事件函数; 
target:{DOMElement|Window},事件节点; 
options:{Object},参数。一个指定有关 listener 属性的可选参数对象。可用的选项如下:
capture:{Boolean},表示 listener 会在该类型的事件捕获阶段传播到该 EventTarget 时触发。 
once:{Boolean},表示 listener 在添加之后最多只调用一次。如果是 true, listener 会在其被调用之后自动移除。 
passive:{Boolean},设置为true时,表示 listener 永远不会调用 preventDefault()。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。查看 使用 passive 改善的滚屏性能 了解更多。 
mozSystemGroup: {Boolean},只能在 XBL 或者是 Firefox’ chrome 使用,表示 listener 被添加到 system group。 
 
 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import  {useEvent, useList} from  'react-use' ;const  Demo = ()  =>  {  const  [list, {push, clear}] = useList();   const  onKeyDown = useCallback(({key} ) =>  {     if  (key === 'r' ) clear();     push(key);   }, []);   useEvent('keydown' , onKeyDown);   return  (     <div>       <p>         Press some keys on your keyboard, <code style={{color : 'tomato' }}>r</code> key resets the list        </ p>      <pre>         {JSON .stringify(list, null , 4 )}       </pre>      </ div>  ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import  { useEffect } from  'react' ;import  { isClient } from  './util' ;export  interface ListenerType1 {  addEventListener(name: string, handler : (event?: any ) =>  void , ...args: any[]);   removeEventListener(name: string, handler : (event?: any ) =>  void , ...args: any[]); } export  interface ListenerType2 {  on(name: string, handler : (event?: any ) =>  void , ...args: any[]);   off(name: string, handler : (event?: any ) =>  void , ...args: any[]); } export  type UseEventTarget = ListenerType1 | ListenerType2;const  defaultTarget = isClient ? window  : null ;const  isListenerType1 = (target: any): target is ListenerType1 => {  return  !!target.addEventListener; }; const  isListenerType2 = (target: any): target is ListenerType2 => {  return  !!target.on; }; type AddEventListener<T> = T extends ListenerType1 ? T['addEventListener' ] : T extends ListenerType2 ? T['on' ] : never; const  useEvent = <T  extends  UseEventTarget > (  name: Parameters<AddEventListener<T>>[0],   handler?: null | undefined | Parameters<AddEventListener<T>>[1],   target: null | T | Window = defaultTarget,   options?: Parameters<AddEventListener<T>>[2] ) => {   useEffect(() => {     if (!handler) {       return;     }     if (!target) {       return;     }     if (isListenerType1(target)) {       target.addEventListener(name, handler, options);     } else if (isListenerType2(target)) {       target.on(name, handler, options);     }     return () => {       if (isListenerType1(target)) {         target.removeEventListener(name, handler, options);       } else if (isListenerType2(target)) {         target.off(name, handler, options);       }     };   }, [name, handler, target, JSON.stringify(options)]); }; export default useEvent; 
 
1.考虑了IE的事件处理; 
2.考虑了非浏览器端的兼容; 
 
mount和unmount的生命周期封装。
语法:1 useLifecycles(mount, unmount); 
参数:
mount:{Function},mount执行回调 
unmount:{Function},unmount执行回调 
 
使用:1 2 3 4 5 6 import  {useLifecycles} from  'react-use' ;const  Demo = ()  =>  {  useLifecycles(()  =>  console .log('MOUNTED' ), () => console .log('UNMOUNTED' ));   return  null ; }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import  { useEffect } from  'react' ;const  useLifecycles = (mount, unmount? ) =>  {  useEffect(()  =>  {     if  (mount) {       mount();     }     return  ()  =>  {       if  (unmount) {         unmount();       }     };   }, []); }; export  default  useLifecycles;
 
不需要解释。。。
组件状态回调钩子。
语法:1 const isMounted = useMountedState(); 
返回:
isMounted:{Function},() => boolean,获取当前是否mount状态 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import  * as  React from  'react' ;import  {useMountedState} from  'react-use' ;const  Demo = ()  =>  {  const  isMounted = useMountedState();   React.useEffect(()  =>  {     setTimeout(()  =>  {       if  (isMounted()) {                } else  {                }     }, 1000 );   }); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import  { useCallback, useEffect, useRef } from  'react' ;export  default  function  useMountedState ( ): ( ) => boolean   {  const  mountedRef = useRef<boolean>(false );   const  get  = useCallback(() => mountedRef.current, []);   useEffect(() => {     mountedRef.current = true ;     return  ()  =>  {       mountedRef.current = false ;     };   });   return  get ; } 
 
通过useEffect来控制mount状态,用ref防止作用域问题。
语法:
1 2 3 const mounted = useUnmountPromise(); mounted(promise, onError); 
 
返回:
mounted:{Function},执行函数。参数:
promise:{Function},回调函数 
onError:{Function},异常捕获处理,可选 
 
 
 
使用:1 2 3 4 5 6 7 8 import  useUnmountPromise from  'react-use/lib/useUnmountPromise' ;const  Demo = ()  =>  {  const  mounted = useUnmountPromise();   useEffect(async  () => {     await  mounted(someFunction());    }); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import  { useMemo, useRef, useEffect } from  'react' ;export  type Race = <P extends Promise<any>, E = any>(promise: P, onError?: (error: E) => void) => P;const useUnmountPromise = (): Race => {   const refUnmounted = useRef(false);   useEffect(() => () => {     refUnmounted.current = true;   });   const wrapper = useMemo(() => {     const race = <P extends Promise<any>, E>(promise: P, onError?: (error: E) => void) => {       const newPromise: P = new Promise((resolve, reject) => {         promise.then(           result => {             if (!refUnmounted.current) resolve(result);           },           error => {             if (!refUnmounted.current) reject(error);             else if (onError) onError(error);             else console.error('useUnmountPromise', error);           }         );       }) as P;       return newPromise;     };     return race;   }, []);   return wrapper; }; export default useUnmountPromise; 
 
promise的处理+ref的缓存。
用于处理函数组件中的包裹promise方法。并且此方法只有当前组件mount后才会resolve。
语法:1 const mounted = usePromise(); 
返回:
mounted:{Function},包裹函数。参数:
promise:{Function},promise方法 
 
 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 import  {usePromise} from  'react-use' ;const  Demo = ({promise} ) =>  {  const  mounted = usePromise();   const  [value, setValue] = useState();   useEffect(()  =>  {     (async  () => {       const  value = await  mounted(promise);              setValue(value);     })();   }); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import  { useCallback } from  'react' ;import  useMountedState from  './useMountedState' ;export  type UsePromise = ()  =>  <T>(promise: Promise<T>) => Promise<T>;const usePromise: UsePromise = () => {   const isMounted = useMountedState();   return useCallback(     (promise: Promise<any>) =>       new Promise<any>((resolve, reject) => {         const onValue = value => {           isMounted() && resolve(value);         };         const onError = error => {           isMounted() && reject(error);         };         promise.then(onValue, onError);       }),     []   ); }; export default usePromise; 
 
简单根据当前组件跟mount状态来进行控制。
包含当前生命周期状态的log控制。
语法:1 useLogger(componentName: string, ...rest); 
参数:
componentName:{String},log内容 
 
使用:1 2 3 4 5 6 import  {useLogger} from  'react-use' ;const  Demo = (props ) =>  {  useLogger('Demo' , props);   return  null ; }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import  useEffectOnce from  './useEffectOnce' ;import  useUpdateEffect from  './useUpdateEffect' ;const  useLogger = (componentName: string, ...rest ) =>  {  useEffectOnce(()  =>  {     console .log(`${componentName}  mounted` , ...rest);     return  ()  =>  console .log(`${componentName}  unmounted` );   });   useUpdateEffect(()  =>  {     console .log(`${componentName}  updated` , ...rest);   }); }; export  default  useLogger;
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import  { useEffect } from  'react' ;import  { useFirstMountState } from  './useFirstMountState' ;const  useUpdateEffect: typeof  useEffect = (effect, deps ) =>  {  const  isFirstMount = useFirstMountState();   useEffect(()  =>  {     if  (!isFirstMount) {       return  effect();     }   }, deps); }; export  default  useUpdateEffect;
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import  { useRef } from  'react' ;export  function  useFirstMountState ( ): boolean   {  const  isFirst = useRef(true );   if  (isFirst.current) {     isFirst.current = false ;     return  true ;   }   return  isFirst.current; } 
 
1.useUpdateEffect是通过useFirstMountState的闭包变量进行的判断,实现控制非首次触发effect的效果(update); 
2.useEffectOnce用于实现mount和unmount的控制回调; 
 
语法:1 useMount(fn: () => void); 
参数:
使用:1 2 3 4 5 6 import  {useMount} from  'react-use' ;const  Demo = ()  =>  {  useMount(()  =>  alert('MOUNTED' ));   return  null ; }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 import  useEffectOnce from  './useEffectOnce' ;const  useMount = (fn: ( ) =>  void ) => {  useEffectOnce(()  =>  {     fn();   }); }; export  default  useMount;
 
不需要解释。。。
组件更新时的effect。
语法:1 useUpdateEffect(fn, deps) 
参数:
fn:{Function},回调函数; 
deps:[Array],依赖项; 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import  React from  'react' import  {useUpdateEffect} from  'react-use' ;const  Demo = ()  =>  {  const  [count, setCount] = React.useState(0 );      React.useEffect(()  =>  {     const  interval = setInterval(()  =>  {       setCount(count  =>  count + 1 )     }, 1000 )          return  ()  =>  {       clearInterval(interval)     }   }, [])      useUpdateEffect(()  =>  {     console .log('count' , count)           return  ()  =>  {             }   })    return  <div > Count: {count}</div >  }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import  { useEffect } from  'react' ;import  { useFirstMountState } from  './useFirstMountState' ;const  useUpdateEffect: typeof  useEffect = (effect, deps ) =>  {  const  isFirstMount = useFirstMountState();   useEffect(()  =>  {     if  (!isFirstMount) {       return  effect();     }   }, deps); }; export  default  useUpdateEffect;
 
useUpdateEffect是通过useFirstMountState的闭包变量进行的判断,实现控制非首次触发effect的效果(update)
根据环境判断使用layoutEffect/effect。
语法:1 useIsomorphicLayoutEffect(effect: EffectCallback, deps?: ReadonlyArray<any> | undefined); 
参数:
effect:{Function},回调 
deps:{Array},依赖列表,等同于useEffect的第二个参数,可选; 
 
使用:1 2 3 4 5 6 7 8 9 import  {useIsomorphicLayoutEffect} from  'react-use' ;const  Demo = ({value} ) =>  {  useIsomorphicLayoutEffect(()  =>  {     window .console.log(value)   }, [value]);   return  null ; }; 
源码及解析 1 2 3 4 5 6 7    import  { useEffect, useLayoutEffect } from  'react' ;const  useIsomorphicLayoutEffect = typeof  window  !== 'undefined'  ? useLayoutEffect : useEffect;export  default  useIsomorphicLayoutEffect;
 
根据执行环境进行了区分使用。
通过依赖项的比较来控制effect执行。
语法:1 useCustomCompareEffect(effect: () => void | (() => void | undefined), deps: any[], depsEqual: (prevDeps: any[], nextDeps: any[]) => boolean); 
effect:{Function},副作用回调 
deps:{Array},依赖列表; 
depsEqual:{Function},比较函数;参数
prevDeps:{Array},比较前依赖项 
nextDeps:{Array},比较后依赖项; 返回:{Boolean},是否需要执行副作用 
 
 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import  {useCounter, useCustomCompareEffect} from  'react-use' ;import  isEqual from  'lodash/isEqual' ;const  Demo = ()  =>  {  const  [count, {inc : inc}] = useCounter(0 );   const  options = { step : 2  };   useCustomCompareEffect(()  =>  {     inc(options.step)   }, [options], (prevDeps, nextDeps) => isEqual(prevDeps, nextDeps));   return  (     <div>       <p>useCustomCompareEffect with  deep comparison: {count}</p>      </ div>  ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import  { DependencyList, EffectCallback, useEffect, useRef } from  'react' ;const  isPrimitive = (val: any ) =>  val !== Object (val);type DepsEqualFnType<TDeps extends DependencyList> = (prevDeps: TDeps, nextDeps: TDeps ) =>  boolean; const  useCustomCompareEffect = <TDeps  extends  DependencyList > (  effect: EffectCallback,   deps: TDeps,   depsEqual: DepsEqualFnType<TDeps> ) => {   if (process.env.NODE_ENV !== 'production') {     if (!(deps instanceof Array) || !deps.length) {       console.warn('`useCustomCompareEffect` should not be used with no dependencies. Use React.useEffect instead.');     }     if (deps.every(isPrimitive)) {       console.warn(         '`useCustomCompareEffect` should not be used with dependencies that are all primitive values. Use React.useEffect instead.'       );     }     if (typeof depsEqual !== 'function') {       console.warn('`useCustomCompareEffect` should be used with depsEqual callback for comparing deps list');     }   }   const ref = useRef<TDeps | undefined>(undefined);   if (!ref.current || !depsEqual(deps, ref.current)) {     ref.current = deps;   }   useEffect(effect, ref.current); }; export default useCustomCompareEffect; 
 
1.用ref来控制useEffect的缓存依赖项 
2.如果依赖项deps都是原始数据类型的话不推荐使用此钩子 
 
使用深比较effect。
语法:1 useDeepCompareEffect(effect: () => void | (() => void | undefined), deps: any[]); 
参数:
effect:{Function},副作用回调 
deps:{Array},依赖列表,等同于useEffect的第二个参数,可选; 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import  {useCounter, useDeepCompareEffect} from  'react-use' ;const  Demo = ()  =>  {  const  [count, {inc : inc}] = useCounter(0 );   const  options = { step : 2  };   useDeepCompareEffect(()  =>  {     inc(options.step)   }, [options]);   return  (     <div>       <p>useDeepCompareEffect: {count}</p>      </ div>  ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25    import  { DependencyList, EffectCallback } from  'react' ;import  { isDeepEqual } from  './util' ;	import  useCustomCompareEffect from  './useCustomCompareEffect' ;const  isPrimitive = (val: any ) =>  val !== Object (val);const  useDeepCompareEffect = (effect: EffectCallback, deps: DependencyList ) =>  {  if  (process.env.NODE_ENV !== 'production' ) {     if  (!(deps instanceof  Array ) || !deps.length) {       console .warn('`useDeepCompareEffect` should not be used with no dependencies. Use React.useEffect instead.' );     }     if  (deps.every(isPrimitive)) {       console .warn(         '`useDeepCompareEffect` should not be used with dependencies that are all primitive values. Use React.useEffect instead.'        );     }   }   useCustomCompareEffect(effect, deps, isDeepEqual); }; export  default  useDeepCompareEffect;
 
使用浅比较effect。
语法:1 useShallowCompareEffect(effect: () => void | (() => void | undefined), deps: any[]); 
参数:
effect:{Function},副作用回调 
deps:{Array},依赖列表,等同于useEffect的第二个参数,可选; 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import  {useCounter, useShallowCompareEffect} from  'react-use' ;const  Demo = ()  =>  {  const  [count, {inc : inc}] = useCounter(0 );   const  options = { step : 2  };   useShallowCompareEffect(()  =>  {     inc(options.step)   }, [options]);   return  (     <div>       <p>useShallowCompareEffect: {count}</p>      </ div>  ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import  { DependencyList, EffectCallback } from  'react' ;import  { equal as  isShallowEqual } from  'fast-shallow-equal' ;import  useCustomCompareEffect from  './useCustomCompareEffect' ;const  isPrimitive = (val: any ) =>  val !== Object (val);const  shallowEqualDepsList = (prevDeps: DependencyList, nextDeps: DependencyList ) =>   prevDeps.every((dep, index ) =>  isShallowEqual(dep, nextDeps[index])); const  useShallowCompareEffect = (effect: EffectCallback, deps: DependencyList ) =>  {  if  (process.env.NODE_ENV !== 'production' ) {     if  (!(deps instanceof  Array ) || !deps.length) {       console .warn('`useShallowCompareEffect` should not be used with no dependencies. Use React.useEffect instead.' );     }     if  (deps.every(isPrimitive)) {       console .warn(         '`useShallowCompareEffect` should not be used with dependencies that are all primitive values. Use React.useEffect instead.'        );     }   }   useCustomCompareEffect(effect, deps, shallowEqualDepsList); }; export  default  useShallowCompareEffect;
 
控制requestAnimationFrame。
语法:1 const elapsed = useRaf(ms?: number, delay?: number): number; 
参数:
ms:{Number},动画总时间,毫秒。默认是1e12,即1e9秒; 
delay:{Number},延迟时间,毫秒。可选; 
 
返回:
使用:1 2 3 4 5 6 7 8 9 10 11 import  {useRaf} from  'react-use' ;const  Demo = ()  =>  {  const  elapsed = useRaf(5000 , 1000 );   return  (     <div>       Elapsed: {elapsed}     </div>    ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import  { useState } from  'react' ;import  useIsomorphicLayoutEffect from  './useIsomorphicLayoutEffect' ;const  useRaf = (ms: number = 1e12 , delay : number = 0 ): number  =>  {  const  [elapsed, set ] = useState<number>(0);   useIsomorphicLayoutEffect(() => {     let  raf;     let  timerStop;     let  start;     const  onFrame = ()  =>  {       const  time = Math .min(1 , (Date .now() - start) / ms);       set (time);       loop();     };     const loop = () => {       raf = requestAnimationFrame(onFrame);     };     const  onStart = ()  =>  {       timerStop = setTimeout(()  =>  {         cancelAnimationFrame(raf);         set (1);       }, ms);       start = Date.now();       loop();     };     const timerDelay = setTimeout(onStart, delay);     return () => {       clearTimeout(timerStop);       clearTimeout(timerDelay);       cancelAnimationFrame(raf);     };   }, [ms, delay]);   return  elapsed; }; export  default  useRaf;
 
比较简单,通过requestAnimationFrame和setTimeout来控制整个运作。
setInterval的控制钩子。为何要单独抽离成一个钩子,可以见Dan Abramov’s article on overreacted.io ,写得非常好了。
语法:1 useInterval(callback, delay?: number) 
参数:
callback:{Function},回调函数; 
delay:{Number},定时器间隔,默认为0。 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import  * as  React from  'react' ;import  {useInterval} from  'react-use' ;const  Demo = ()  =>  {  const  [count, setCount] = React.useState(0 );   const  [delay, setDelay] = React.useState(1000 );   const  [isRunning, toggleIsRunning] = useBoolean(true );   useInterval(     () => {       setCount(count + 1 );     },     isRunning ? delay : null    );   return  (     <div>       <div>         delay: <input  value ={delay}  onChange ={event  =>  setDelay(Number(event.target.value))} />       </div>        <h1>count: {count}</ h1>      <div>         <button onClick={toggleIsRunning}>{isRunning ? 'stop'  : 'start' }</button>        </ div>    </div>    ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import  { useEffect, useRef } from  'react' ;const  useInterval = (callback: Function , delay?: number | null  ) =>  {  const  savedCallback = useRef<Function >(( ) =>  {});   useEffect(()  =>  {     savedCallback.current = callback;   });   useEffect(()  =>  {     if  (delay !== null ) {       const  interval = setInterval(()  =>  savedCallback.current(), delay || 0 );       return  ()  =>  clearInterval(interval);     }     return  undefined ;   }, [delay]); }; export  default  useInterval;
 
ref作为动画id避免函数组件的作用域问题。
同样是setInterval的控制钩子,但是所有的effect会在同样的延迟进行。
语法:1 useHarmonicIntervalFn(fn, delay?: number) 
参数:
fn:{Function},回调函数; 
delay:{Number},定时器间隔,默认为0。 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import  * as  React from  'react' ;import  {useHarmonicIntervalFn} from  'react-use' ;const  Demo = ()  =>  {  const  [count, setCount] = React.useState(0 );   const  [delay, setDelay] = React.useState(1000 );   const  [isRunning, toggleIsRunning] = useBoolean(true );   useHarmonicIntervalFn(     () => {       setCount(count + 1 );     },     isRunning ? delay : null    );   return  (     <div>       <div>         delay: <input  value ={delay}  onChange ={event  =>  setDelay(Number(event.target.value))} />       </div>        <h1>count: {count}</ h1>      <div>         <button onClick={toggleIsRunning}>{isRunning ? 'stop'  : 'start' }</button>        </ div>    </div>    ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22    import  { useEffect, useRef } from  'react' ;import  { setHarmonicInterval, clearHarmonicInterval } from  'set-harmonic-interval' ;const  useHarmonicIntervalFn = (fn: Function , delay: number | null  = 0  ) =>  {  const  latestCallback = useRef<Function >(( ) =>  {});   useEffect(()  =>  {     latestCallback.current = fn;   });   useEffect(()  =>  {     if  (delay !== null ) {       const  interval = setHarmonicInterval(()  =>  latestCallback.current(), delay);       return  ()  =>  clearHarmonicInterval(interval);     }     return  undefined ;   }, [delay]); }; export  default  useHarmonicIntervalFn;
 
核心的实现依赖于set-harmonic-interval 这个库。
弹簧力学的动力钩子。需要安装rebound 
语法:1 const currentValue = useSpring(targetValue, tension, friction); 
参数:
targetValue:{Number},目标值。可选,默认为0; 
tension:{Number},张力。可选,默认为50; 
friction:{Number},摩擦力。可选,默认为50。 
 
返回:
currentValue:{Number},当前值 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import  useSpring from  'react-use/lib/useSpring' ;const  Demo = ()  =>  {  const  [target, setTarget] = useState(50 );   const  value = useSpring(target);   return  (     <div>       {value}       <br />       <button onClick={() => setTarget(0 )}>Set  0 </button>        <button onClick={() => setTarget(100)}>Set 100</ button>    </div>    ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import  { useEffect, useMemo, useState } from  'react' ;import  { Spring, SpringSystem } from  'rebound' ;const  useSpring = (targetValue: number = 0 , tension: number = 50 , friction: number = 3  ) =>  {  const  [spring, setSpring] = useState<Spring | null >(null );   const  [value, setValue] = useState<number>(targetValue);         const  listener = useMemo(     () => ({       onSpringUpdate: currentSpring  =>  {         const  newValue = currentSpring.getCurrentValue();         setValue(newValue);       },     }),     []   );   useEffect(()  =>  {     if  (!spring) {       const  newSpring = new  SpringSystem().createSpring(tension, friction);       newSpring.setCurrentValue(targetValue);       setSpring(newSpring);       newSpring.addListener(listener);     }     return  ()  =>  {       if  (spring) {         spring.removeListener(listener);         setSpring(null );       }     };   }, [tension, friction, spring]);   useEffect(()  =>  {     if  (spring) {       spring.setEndValue(targetValue);     }   }, [targetValue]);   return  value; }; export  default  useSpring;
 
对rebound的封装。
控制setTimeout。
语法:1 2 3 4 5 const [     isReady: () => boolean | null,     cancel: () => void,     reset: () => void, ] = useTimeoutFn(fn: Function, ms: number = 0); 
参数:
fn:{Function},回调方法; 
ms:{Number},延迟时间,毫秒。默认是0; 
 
返回:
functions:{Function[]},控制方法集合
0:() => boolean | null,当前是否已经完成; 
1:() => void,取消方法; 
2:() => void,重置方法 
 
 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import  * as  React from  'react' ;import  { useTimeoutFn } from  'react-use' ;const  Demo = ()  =>  {  const  [state, setState] = React.useState('Not called yet' );   function  fn ( )  {     setState(`called at ${Date .now()} ` );   }   const  [isReady, cancel, reset] = useTimeoutFn(fn, 5000 );   const  cancelButtonClick = useCallback(()  =>  {     if  (isReady() === false ) {       cancel();       setState(`cancelled` );     } else  {       reset();       setState('Not called yet' );     }   }, []);   const  readyState = isReady();   return  (     <div>       <div>{readyState !== null  ? 'Function will be called in 5 seconds'  : 'Timer cancelled' }</div>        <button onClick={cancelButtonClick}> {readyState === false ? 'cancel' : 'restart'} timeout</ button>      <br />       <div>Function  state: {readyState === false  ? 'Pending'  : readyState ? 'Called'  : 'Cancelled' }</div>        <div>{state}</ div>    </div>    ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import  { useCallback, useEffect, useRef } from  'react' ;export  type UseTimeoutFnReturn = [()  =>  boolean | null , () => void , () => void ];export  default  function  useTimeoutFn (fn: Function, ms: number = 0  ): UseTimeoutFnReturn   {  const  ready = useRef<boolean | null >(false );   const  timeout = useRef<ReturnType<typeof  setTimeout>>();   const  callback = useRef(fn);   const  isReady = useCallback(()  =>  ready.current, []);   const  set  = useCallback(() => {     ready.current = false ;     timeout.current && clearTimeout(timeout.current);     timeout.current = setTimeout(()  =>  {       ready.current = true ;       callback.current();     }, ms);   }, [ms]);   const  clear = useCallback(()  =>  {     ready.current = null ;     timeout.current && clearTimeout(timeout.current);   }, []);      useEffect(()  =>  {     callback.current = fn;   }, [fn]);      useEffect(()  =>  {     set ();     return clear;   }, [ms]);   return [isReady, clear, set ]; } 
 
ref作为定时器id用来避免作用域问题。并且考虑了unmount时的定时器清除。
控制setTimeout。
语法:1 2 3 4 5 const [     isReady: () => boolean | null,     cancel: () => void,     reset: () => void, ] = useTimeout(ms: number = 0); 
参数:
返回:
functions:{Function[]},控制方法集合
0:() => boolean | null,当前是否已经完成; 
1:() => void,取消方法; 
2:() => void,重置方法 
 
 
 
使用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import  { useTimeout } from  'react-use' ;function  TestComponent (props: { ms?: number } = {} )  {  const  ms = props.ms || 5000 ;   const  [isReady, cancel] = useTimeout(ms);   return  (     <div>       { isReady() ? 'I\'m reloaded after timeout'  : `I will be reloaded after ${ ms / 1000 }s` }          { isReady() === false ? <button onClick={ cancel }>Cancel</ button> : ''  }    </div>   ); } const Demo = () => {   return (     <div>       <TestComponent />       <TestComponent ms={ 10000 } />     </div>   ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 import  useTimeoutFn from  './useTimeoutFn' ;import  useUpdate from  './useUpdate' ;export  type UseTimeoutReturn = [()  =>  boolean | null , () => void , () => void ];export  default  function  useTimeout (ms: number = 0  ): UseTimeoutReturn   {  const  update = useUpdate();   return  useTimeoutFn(update, ms); } 
 
基于useTimeoutFn,实现组件延迟更新render。
动画过渡钩子。
语法:1 const current = useTween(easing?: string, ms?: number, delay?: number): number 
参数:
easing:{String},动画效果,字可见字段集合 。默认为"inCirc"; 
ms:{Number},动画总时间,毫秒。默认是200; 
delay:{Number},延迟时间,毫秒。默认是0; 
 
返回:
使用:1 2 3 4 5 6 7 8 9 10 11 import  {useTween} from  'react-use' ;const  Demo = ()  =>  {  const  t = useTween();   return  (     <div>       Tween: {t}     </div>    ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import  { easing } from  'ts-easing' ;import  useRaf from  './useRaf' ;export  type Easing = (t: number ) =>  number;const  useTween = (easingName: string = 'inCirc' , ms : number = 200 , delay : number = 0 ): number  =>  {  const  fn: Easing = easing[easingName];   const  t = useRaf(ms, delay);   if  (process.env.NODE_ENV !== 'production' ) {     if  (typeof  fn !== 'function' ) {       console .error(         'useTween() expected "easingName" property to be a valid easing function name, like:'  +           '"'  +           Object .keys(easing).join('", "' ) +           '".'        );       console .trace();       return  0 ;     }   }   return  fn(t); }; export  default  useTween;
 
依赖于ts-easing 动画库,动画控制核心依赖于requestAnimationFrame。
重新渲染组件。
语法:1 const update = useUpdate(); 
返回:
使用:1 2 3 4 5 6 7 8 9 10 11 import  {useUpdate} from  'react-use' ;const  Demo = ()  =>  {  const  update = useUpdate();   return  (     <>       <div>Time: {Date .now()}</div>        <button onClick={update}>Update</ button>    </>    ); }; 
源码及解析 1 2 3 4 5 6 7 8 9 10 11 12 import  { useReducer } from  'react' ;const  updateReducer = (num: number): number  =>  (num + 1 ) % 1 _000_000;const  useUpdate = ()  =>  {  const  [, update] = useReducer(updateReducer, 0 );   return  update as  () => void ; }; export  default  useUpdate;
 
依赖于useReducer,以计数的形式来触发组件的更新; 
1_000_000其实就是1000000; 
 
Author
My name is Micheal Wayne and this is my blog.
I am a front-end software engineer.
Contact: michealwayne@163.com