一、基本钩子
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 Hook3、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}-`,
});