React专题—Hooks干货总结

966 阅读9分钟

一、基本钩子

1、useState

作用:可以在函数组件里面使用类似class的setState功能来维护自己的state。

示例:

const [name, setName] = useState('rose');

注意事项:useState的初始值,只在第一次有效。

2、useEffect

作用:可以在函数组件里面使用class的生命周期函数componentDidMount、componentDidUpdate和componentWillUnMount。

示例:

useEffect(() => {
  console.log('use effect...');
  const onClick = ()=>{
    setCount(count+1);
  }
  btnRef.current.addEventListener('click',onClick, false);
  return ()=> btnRef.current.removeEventListener('click',onClick, false);
},[count])

useEffect(() => {
  const timer = setInterval(() => setCount(count +1), 1000);
  return ()=> clearInterval(timer);
})

注意事项:

a. useEffect里面使用到的state的值,固定在了useEffect内部,不会被改变,除非useEffect刷新,重新固定state的值

示例:

const [count, setCount] = useState(0);
useEffect(() => {
  console.log('use effect...',count);
  const timer = setInterval(() => {
    console.log('timer...count:', count);
    setCount(count + 1);
  }, 1000)
  return ()=> clearInterval(timer);
},[])

b. useEffect不能被判断包裹

示例:

const [count, setCount] = useState(0);
if(2 < 5){
  useEffect(() => {
    console.log('use effect...',count);
    const timer = setInterval(() => setCount(count +1), 1000);
    return ()=> clearInterval(timer);
  })
}

c. useEffect不能被打断

示例:

const [count, setCount] = useState(0);
useEffect(...);
return; // 函数提前结束了
useEffect(...);
}

3、useContext

作用:跨代组件共享状态。

示例:

import React, {useContext, useReducer} from 'react';

const reducer = (state = 0, {type}) => {
  switch (type) {
    case "add":
      return state + 1
    case 'delete':
      return state - 1
    default:
      return state;
  }
}

const Context = React.createContext(null);

const Child = () => {
  const [count, dispatch] = useContext(Context);
  return (
    <div>
      <div>child...{count}</div>
      <button onClick={() => dispatch({type: 'add'})}>child add</button>
      <button onClick={() => dispatch({type: 'delete'})}>child delete</button>
    </div>
    )
}

const Hook = () => {
  const [count, dispatch] = useReducer(reducer, 10)
  return (
    <Context.Provider value={[count, dispatch]}>
      <div>
        <div>mom ... {count}</div>
        <Child/>
        <button onClick={() => dispatch({type: 'add'})}>mom add</button>
        <button onClick={() => dispatch({type: 'delete'})}>mom delete</button>
            </div>
        </Context.Provider>
    )
}
export default Hook

二、拓展钩子

1、useCallback

作用:解决了函数的缓存的问题。

例如,点击按钮触发一个事件回调onChange,每次点击按钮都会重新生成一个新的onChange,然后赋值给相应组件,由于浅比较失败,从而导致该组件重新render,尽管组件什么都没有做。

示例:

const onChange = useCallback((e)=>{
  setText(e.target.value());
},[])

2、useReducer

作用:状态管理,用来管理复杂一点的数据。

示例:

const reducer =(state = 0, {type})=>{
  switch (type) {
    case "add":
      return state+1;
    case 'delete':
      return state-1;
    default:
      return state;;
  }
}

const Hook =()=>{
  const [count, dispatch] = useReducer(reducer, 0);
  return(
    <div>
      count:{count}
      <button onClick={()=> dispatch({type:'add'})}>add</button>
      <button onClick={()=> dispatch({type:'delete'})}>delete</button>
    </div>
    )
}
export default Hook

3、useMemo

作用:解决值的缓存的问题。

示例:

const Child = memo(({data}) =>{
   console.log('child render...', data.name);
   return (
       <div>
           <div>child</div>
           <div>{data.name}</div>
       </div>
   );
})

const Hook =()=>{
   console.log('Hook render...');
   const [count, setCount] = useState(0);
   const [name, setName] = useState('rose');
   const data = useMemo(()=>{
     return { name };
   },[name]);
  
   return(
       <div>
           <div>{count}</div>
           <button onClick={()=>setCount(count+1)}>update count </button>
           <Child data={data}/>
       </div>
   )
}

4、useRef

作用:相当于全局作用域,一处被修改,其他地方全更新;普遍操作,用来操作dom;用来存储一些代码块。

示例:

const [count, setCount] = useState(0);
const countRef = useRef(0);

useEffect(() => {
   console.log('use effect...',count);
   const timer = setInterval(() => {
       console.log('timer...count:', countRef.current);
       setCount(countRef.current + 1);
   }, 1000);
   return ()=> clearInterval(timer);
},[])

const Hook =()=>{
   const [count, setCount] = useState(0);
   const btnRef = useRef(null);
   useEffect(() => {
       console.log('use effect...');
       const onClick = ()=>{
           setCount(count+1);
       };
       btnRef.current.addEventListener('click',onClick, false);
       return ()=> btnRef.current.removeEventListener('click',onClick, false);
   },[count])
   return(
       <div>
           <div>{count}</div>
           <button ref={btnRef}>click me </button>
       </div>
   )
}

5、useLayoutEffect

作用:通过同步执行状态更新,可解决一些特性场景下的页面闪烁问题,但会阻塞渲染,请谨慎使用。

示例:

function App() {
 const [count, setCount] = useState(0);
 useLayoutEffect(() => {
   if (count === 0) {
     const randomNum = 10 + Math.random()*200;
     setCount(10 + Math.random()*200);
   }
 }, [count]);
 return (
     <div onClick={() => setCount(0)}>{count}</div>
 );
}

注意事项:useLayoutEffect内部状态修改后,才会去更新页面。

6、useImperativeHandle

作用:可以让你在使用 ref 时,自定义暴露给父组件的实例值。

示例:

function Child(props, parentRef){
   let focusRef = useRef();
   let inputRef = useRef();
  
   useImperativeHandle(parentRef, ()=>(
       return {
           focusRef,
           inputRef,
           name:'计数器',
           focus(){
               focusRef.current.focus();
           },
           changeText(text){
               inputRef.current.value = text;
           }
       }
   });

   return (
       <>
           <input ref={focusRef}/>
           <input ref={inputRef}/>
       </>
   )
}

Child = forwardRef(Child);

function Parent(){
 const parentRef = useRef();
 function getFocus(){
   parentRef.current.focus();
   parentRef.current.addNumber(666);
   parentRef.current.changeText('<script>alert(1)</script>');
   console.log(parentRef.current.name);
 }
  
 return (
     <>
       <Child ref={parentRef}/>
       <button onClick={getFocus}>获得焦点</button>
     </>
 )
}

7、useDebugValue

作用:用于在React DevTools中显示自定义钩子的标签,对于自定义钩子中用于共享的部分有更大价值;

自定义显示格式。

示例:

useDebugValue(date, date => date.toDateString());

三、自定义封装的hooks

1、useCallbackState

作用:类似于类组件里的setState,相比useState,多了第二个参数,即回调函数callback。

示例:

import { useEffect, useRef, useState } from 'react';

const useCallbackState = (initState) => {
    const [state, setState] = useState(initState);
    let isUpdate = useRef();
    const setCallbackState = (state, cb) => {
      setState(prev => {
        isUpdate.current = cb;
        return typeof state === 'function' ? state(prev) : state;
      });
    };
    useEffect(() => {
      if(isUpdate.current) {
        isUpdate.current();
      }
    })
  
    return [state, setCallbackState];
  }

export default useCallbackState;

2、usePrevious

作用:用于获取之前的值

示例:

const usePrevious = value => {
 const ref = useRef();
 useEffect(() => {
   ref.current = value;
 });
 return ref.current;
};

export default usePrevious;

3、useDeepCompareMemoize

作用:用于深度比较记忆和存储

示例:

const useDeepCompareMemoize = (value) => {
 const ref = React.useRef();
 if (!isEqual(value, ref.current)) {
   ref.current = value;
 }
 return ref.current;
}

export default useDeepCompareMemoize;

4、useDeepCompareCallback

作用:在useCallback的基础上,对第二个依赖数组参数添加了记忆功能

示例:

const useDeepCompareCallback = (cb, dependencies) => {
 return useCallback(cb, useDeepCompareMemoize(dependencies));
};

export default useDeepCompareCallback;

5、useDeepCompareEffect

作用:在useEffect的基础上,对第二个依赖数组参数添加了记忆功能

示例:

const useDeepCompareEffect = (cb, dependencies) => {
 useEffect(cb, useDeepCompareMemoize(dependencies));
};

export default useDeepCompareEffect;

6、usePagination

作用:用于分页相关的操作和存储

示例:

import { useState, useCallback } from 'react';

const usePagination = (defaultCurPage = 1, startPage = 1, _total) => {
 const [curPage, setCurPage] = useState(defaultCurPage);
 const [total, setPageTotal] = useState(_total);
  
 const setPage = page => {
   if (page > startPage + total) {
     process.env.NODE_ENV !== 'production' && console.warn(`[usePagination] given page is greater than the given range ${startPage} - ${startPage + total}`);
   }
   if (page < startPage) {
     process.env.NODE_ENV !== 'production' && console.warn(`[usePagination] given page is less than the given range ${startPage} - ${startPage + total}`);
   }
   return setCurPage(page);
 };
  
 const incrPage = useCallback(() => {
   setCurPage(Math.min(curPage + 1, total + startPage));
 }, [curPage, total]);
  
 const decrPage = useCallback(() => {
   setCurPage(Math.max(curPage - 1, startPage));
 }, [curPage]);
  
 return [curPage, total, setPageTotal, setPage, incrPage, decrPage];
};

const usePagination = (initial: number = 1) => {
  const [pageNum, setPageNum] = useState(initial);
  // 上一页 & 下一页 依赖pageNum状态
  const nextPage = useCallback(() => setPageNum(pageNum + 1), [pageNum]);
  const prevPage = useCallback(() => setPageNum(pageNum - 1), [pageNum]);
  const gotoPage = useCallback((num: number) => setPageNum(num), []);
  return { pageNum, nextPage, prevPage, gotoPage };
};

export default usePagination;

7、useScroll

作用:用于滚动防抖

示例:

const useScroll = (debouncePeriod = 100) => {
 const [state, setState] = useState({
   y: window.pageYOffset,
   x: window.pageXOffset,
 });
  
 let timer = null;
  
 const onScroll = () => {
   if (timer) {
     clearTimeout(timer);
   }
   timer = setTimeout(() => {
     setState(() => ({
       y: window.pageYOffset,
       x: window.pageXOffset,
     }));
   }, debouncePeriod);
 };
  
 useEffect(() => {
   window.addEventListener('scroll', onScroll, false);
   return () => {
     if (timer) {
       clearTimeout(timer);
     }
     window.removeEventListener('scroll', onScroll, false);
   };
 }, []);
  
 return [state.x, state.y];
};

export default useScroll;

8、useQuery

作用:用于异步请求时的状态管理

示例:

const useQuery = () => {
 const [queried, setQueried] = useState(false);
 const [finished, setFinished] = useState(false);
 const [loading, setLoading] = useState(true);
 const [error, setError] = useState(undefined);
  
 const query = useCallback((asyncCb = () => {}) => {
   let cancelled = false;
   
   if (!cancelled) {
     setError(undefined);
     setQueried(true);
     setLoading(() => true);
   }
   
   if (!cancelled) {
     asyncCb(cancelled)
       .then(() => {
         if (!cancelled) {
           setFinished(true);
         }
       })
       .catch(e => {
         if (!cancelled) {
           setError(e);
           console.error(e); // eslint-disable-line no-console
         }
       })
       .finally(() => {
         if (!cancelled) {
           setLoading(false);
         }
       });
   }
   
   return () => {
     cancelled = true;
     // process.env.NODE_ENV === 'development' && console.warn('[useQuery] query has been cancelled');
   };
 }, []);
  
 return [query, finished, loading, error, queried];
};

export default useQuery;

9、useWindowSize

作用:自定义一个当resize 的时候监听window的width和height的hook

示例:

const useWindowSize = () => {
   const [width, setWidth] = useState();
   const [height, setHeight] = useState();
  
   useEffect(() => {
       const {clientWidth, clientHeight} = document.documentElement;
       setWidth(clientWidth);
       setHeight(clientHeight);
   }, []);
  
   useEffect(() => {
       const handleWindowSize = () =>{
           const {clientWidth, clientHeight} = document.documentElement;
           setWidth(clientWidth);
           setHeight(clientHeight);
       };
       window.addEventListener('resize', handleWindowSize, false);
       return () => {
           window.removeEventListener('resize',handleWindowSize, false);
       }
   });
  
   return [width, height]
}

export default useWindowSize;

10、useUpdate

作用:强制更新,让组件重新渲染。

示例:

const useUpdate = () => {
   const [, setFlag] = useState();
   const update = () => {
       setFlag(Date.now());
   }
 
   return update;
}

export default useUpdate;

11、useDocumentTitle

作用:自定义页面的标题

示例:

const useDocumentTitle = (title) => {
   useEffect(() => {
     document.title = title;
   }, [])
 
   return;
}
export default useDocumentTitle;

12、useDebounce

作用:防抖

示例:

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;

13、useThrottle

作用:节流

示例:

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;

14、useInput

作用:将表单控件的状态管理逻辑抽象出来复用。

// useInput.tsx
import { useState } from 'react';

const useInput = (initialValue: string) => {
  const [value, setValue] = useState(initialValue);
  const reset = () => {
    setValue(initialValue)
  };
  const bind = {
    value,
    onChange(e: any) {
      setValue(e.target.value);
    }
  };
  return [value, bind, reset];
}

export default useInput;

// UserForm.tsx
import React, { FormEvent } from 'react';
import useInput from './hooks/useInput';

function UserForm() {
  const [firstName, bindFirstName, resetFirstName] = useInput('');
  const [lastName, bindLastName, resetLastName] = useInput('');

  const submitHandler = (e: FormEvent) => {
    e.preventDefault();
    resetFirstName();
    resetLastName();
  };
  return (
    <div>
      <form onSubmit={submitHandler}>
        <div>
          <label htmlFor="">First name</label>
          <input
            type="text"
            {...bindFirstName}
          />
        </div>
        <div>
          <label htmlFor="">Last name</label>
          <input
            type="text"
            {...bindLastName}
          />
        </div>
        <button>submit</button>
      </form>
    </div>
  )
}

export default UserForm;

15、useModal

作用:控制弹窗的显示和关闭

import { useState } from 'react';

const useModal = (state=false) => {
  const [visible, setVisible] = useState(state);
  const onCloseModal = () => {
    setVisible(false);
  }

  const onShowModal = () => {
    setVisible(true);
  }

  return { visible, setVisible, onCloseModal, onShowModal };
}

export default useModal;

16、useLoading

作用:请求数据时加载loading效果。

import { useState, useCallback } from 'react';

const useLoading = (loading=false) => {
  const [loading, setLoading] = useState(loading);
  const openLoading = () => {
    setLoading(true);
  }
  
  const closeLoading = () => {
    setLoading(false);
  }
  
  return [loading, openLoading, closeLoading];
};

export default useLoading;

17、useClickOutside

作用:点击元素外的空白区域事件封装

import { useEffect } from 'react';

const useClickOutside = (el, callback) => {
  const handleClickOutside = (e) => {
    if (el && !el.contains(e.target)) {
      callback();
    }
  };

  useEffect(() => {
    window.addEventListener('click', handleClickOutside);
    return () => {
      window.removeEventListener('click', handleClickOutside);
    };
  });
};

export default useClickOutside;

18、useQueryParams

作用:处理请求参数的逻辑封装

import { useState } from 'react';

const useQueryParams = (params) => {
  const [queryParams, setQueryParams] = useState(params);

  const updateQueryParams = (payload = {}) => {
    setQueryParams((prevParams) => ({
      ...prevParams,
      ...payload,
    }));
  }

  return { queryParams, updateQueryParams };
};

export default useQueryParams;

四、useReducer+useContext实现一个小型redux

// actionType.js
const actionType = {
 INSREMENT: 'INSREMENT',
 DECREMENT: 'DECREMENT',
 RESET: 'RESET'
}
export default actionType

// actions.js
import actionType from './actionType'

const add = (num) => ({
   type: actionType.INSREMENT,
   payload: num
})

const dec = (num) => ({
   type: actionType.DECREMENT,
   payload: num
})

const getList = (data) => ({
   type: actionType.GETLIST,
   payload: data
})

export { add, dec, getList };

// reducer.js
function init(initialCount) {
 return {
   count: initialCount,
   total: 10,
   user: {},
   article: []
 };
}

function reducer(state, action) {
 switch (action.type) {
   case actionType.INSREMENT:
     return {count: state.count + action.payload};
   case actionType.DECREMENT:
     return {count: state.count - action.payload};
   case actionType.RESET:
     return init(action.payload);
   default:
     throw new Error();
 }
}
export { init, reducer };

// redux.js
import React, { useReducer, useContext, createContext } from 'react';
import { init, reducer } from './reducer';
const Context = createContext();

const Provider = (props) => {
 const [state, dispatch] = useReducer(reducer, props.initialState || 0, init);
   return (
     <Context.Provider value={{state, dispatch}}>
       { props.children }
     </Context.Provider>
   )
}
export { Context, Provider };

五、useRequest使用

基础网络请求:

import { useRequest } from '@umijs/hooks';

function getUsername() {
  return Promise.resolve('jack');
}

export default () => {
  const { data, error, loading } = useRequest(getUsername);
  
  if (error) return <div>failed to load</div>
  if (loading) return <div>loading...</div>
  return <div>Username: {data}</div>
}

手动请求:手动触发数据请求

import { useRequest } from '@umijs/hooks';

export default () => {
  const { run, loading } = useRequest(changeUsername, { manual: true });
  
  return (
    <Button onClick={() => run('new name')} loading={loading}>
       Edit
    </Button>
    )
}

轮询:需要不断发起网络请求以更新数据

import { useRequest } from '@umijs/hooks';

export default () => {
  const { data } = useRequest(getUsername, { pollingInterval: 1000 });

  return <div>Username: {data}</div>
}

并行请求:相同分类的请求,维护一份状态;不同分类的请求,维护多份状态

export default () => {
  const { run, fetches } = useRequest(deleteUser, {
    manual: true,
    fetchKey: id => id, // 不同的 ID,分类不同
  });

  return (
    <div>
      <Button loading={fetches.A?.loading} onClick={() => { run('A') }}>删除 1</Button>
      <Button loading={fetches.B?.loading} onClick={() => { run('B') }}>删除 2</Button>
      <Button loading={fetches.C?.loading} onClick={() => { run('C') }}>删除 3</Button>
    </div>
  );
};

防抖 & 节流:

import { useRequest } from '@umijs/hooks';

export default () => {
  const { data, loading, run, cancel } = useRequest(getEmail, {
    debounceInterval: 500,
    manual: true
  });

  return (
    <div>
      <Select onSearch={run} loading={loading}>
        {data && data.map(i => <Option key={i} value={i}>{i}</Option>)}
      </Select>
    </div>
  );
};

缓存 & SWR & 预加载:

const { data, loading } = useRequest(getArticle, {
  cacheKey: 'articleKey',
});

屏幕聚焦重新请求:

const { data, loading } = useRequest(getArticle, {
  refreshOnWindowFocus: true,
});

集成请求库:

// 用法 1
const { data, error, loading } = useRequest('/api/userInfo');

// 用法 2
const { data, error, loading } = useRequest({
  url: '/api/changeUsername',
  method: 'post',
});

// 用法 3
const { data, error, loading, run } = useRequest((userId)=> `/api/userInfo/${userId}`);

// 用法 4
const { loading, run } = useRequest((username) => ({
  url: '/api/changeUsername',
  method: 'post',
  data: { username },
}));

分页:

import { useRequest } from '@umijs/hooks';

export default () => {
  const [gender, setGender] = useState('male');
  const { tableProps } = useRequest((params)=>{
    return getTableData({...params, gender})
  }, {
    paginated: true,
    refreshDeps: [gender]
  });

  const columns = [];

  return (
    <Table columns={columns} rowKey="email" {...tableProps}/>
  );
};

加载更多:

const { data, loading, loadMore, loadingMore } = useRequest((d) => getLoadMoreList(d?.nextId, 3), {
  loadMore: true,
  loadingDelay: 500,
  cacheKey: 'loadMoreDemoCacheId',
  fetchKey: d => `${d?.nextId}-`,
});