- useSetState:管理 object 类型 state
- useBoolean:管理 boolean 状态
- useToggle:接收两个状态值,设置两个状态值来回切换
- useUrlState:通过 url query 来管理 state
- useLocalStorageState:将状态传入到 localStorage 里面
- useSessionStorageState:将状态传入 sessionStorage 里面
- useDebounce:处理防抖【值】
- useThrottle:处理节流【值】
- useMap:管理 Map 状态
- useSet:管理 Set 状态
- usePrevious:保存上一个状态
- useSafeState:组件卸载后,异步回调内的 setState 不再执行,避免因组件卸载后更新状态而导致的内存泄漏问题
- useGetState:扩展 React.useState 方法,增加一个 getter,获取最新的值
useSetState:管理 object 类型 state
主要原因是因为 useState 不会自动合并更新对象,大部分情况下需要我们自己手动合并,因此提供了 useSetState hooks 来解决这个问题
const useSetState = <S extends Record<string, any>>(initialState: S | (() => S),): [S, SetState<S>] => {const [state, setState] = useState<S>(initialState);const setMergeState = useCallback((patch) => {setState((prevState) => {// 判断是否为 function,若是则调用 function 获取状态const newState = isFunction(patch) ? patch(prevState) : patch;// 合并return newState ? { ...prevState, ...newState } : prevState;});}, []);return [state, setMergeState];};
useBoolean:管理 boolean 状态
export default function useBoolean(defaultValue = false): [boolean, Actions] {const [state, { toggle, set }] = useToggle(defaultValue);// 函数不会变,使用 useMemoconst actions: Actions = useMemo(() => {const setTrue = () => set(true); // 设置 trueconst setFalse = () => set(false); // 设置 falsereturn {toggle, // 切换set: (v) => set(!!v), // 设置值setTrue,setFalse,};}, []);return [state, actions];}
useToggle:接收两个状态值,设置两个状态值来回切换
useToggle() 不传参数,则效果和 useBoolean 是一致的。
export interface Actions<T> {setLeft: () => void;setRight: () => void;set: (value: T) => void;toggle: () => void;}function useToggle<T = boolean>(): [boolean, Actions<T>];function useToggle<T>(defaultValue: T): [T, Actions<T>];function useToggle<T, U>(defaultValue: T, reverseValue: U): [T | U, Actions<T | U>];function useToggle<D, R>(defaultValue: D = false as unknown as D, reverseValue?: R) {const [state, setState] = useState<D | R>(defaultValue);const actions = useMemo(() => {const reverseValueOrigin = (reverseValue === undefined ? !defaultValue : reverseValue) as D | R;// 切换,跟上一个值判断是不是一样,然后切另一个const toggle = () => setState((s) => (s === defaultValue ? reverseValueOrigin : defaultValue));const set = (value: D | R) => setState(value); // 设置值const setLeft = () => setState(defaultValue); // 设置左值const setRight = () => setState(reverseValueOrigin); // 设置右值return {toggle,set,setLeft,setRight,};// useToggle ignore value change// }, [defaultValue, reverseValue]);}, []);return [state, actions];}
useUrlState:通过 url query 来管理 state
- navigateMode 路由方式支持 push 或者 replace
- url 参数解析主要使用 query-string 这个库:https://www.npmjs.com/package/query-string
- 原来就有的 url 参数不会被清除 ```javascript export interface Options { navigateMode?: ‘push’ | ‘replace’; }
// 这些是 query-string 使用的参数 const parseConfig = { skipNull: false, skipEmptyString: false, parseNumbers: false, parseBooleans: false, };
type UrlState = Record
const useUrlState = (
initialState?: S | (() => S),
options?: Options,
) => {
type State = Partial<{ [key in keyof S]: any }>;
const { navigateMode = ‘push’ } = options || {};
const location = rc.useLocation();
// react-router v5 const history = rc.useHistory?.(); // react-router v6 const navigate = rc.useNavigate?.();
const update = useUpdate();
// hooks 传入进来的 initialState const initialStateRef = useRef( typeof initialState === ‘function’ ? (initialState as () => S)() : initialState || {}, );
// 获取当前 url 上的参数 const queryFromUrl = useMemo(() => { return parse(location.search, parseConfig); }, [location.search]);
// 传入进来的 initialState 与 当前 url 上的 query 参数合并 const targetQuery: State = useMemo( () => ({ …initialStateRef.current, …queryFromUrl, }), [queryFromUrl], );
const setState = (s: React.SetStateAction
// 1. 如果 setState 后,search 没变化,就需要 update 来触发一次更新。比如 demo1 直接点击 clear,就需要 update 来触发更新。// 2. update 和 history 的更新会合并,不会造成多次更新update();// 主要兼容了两个版本 react-router 的 api,一个是 history,一个是 navigateif (history) {history[navigateMode]({hash: location.hash,search: stringify({ ...queryFromUrl, ...newQuery }, parseConfig) || '?',});}if (navigate) {navigate({hash: location.hash,search: stringify({ ...queryFromUrl, ...newQuery }, parseConfig) || '?',},{replace: navigateMode === 'replace',},);}
};
return [targetQuery, useMemoizedFn(setState)] as const; };
<a name="UsPUK"></a>## useCookieState:存储 cookie- cookies 主要使用 js-cookie:[https://github.com/js-cookie/js-cookie](https://github.com/js-cookie/js-cookie)- useCookieState 的时候可以传入 js-cookie 的 option,在 updateState 的时候也可以传入,会对两者进行合并```javascriptexport type State = string | undefined;// 基于 cookies 的 options 再扩展多一个属性 defaultValueexport interface Options extends Cookies.CookieAttributes {defaultValue?: State | (() => State);}// 传入 cookieKey,还有 js-cookie 的 optionsfunction useCookieState(cookieKey: string, options: Options = {}) {const [state, setState] = useState<State>(() => {const cookieValue = Cookies.get(cookieKey);if (typeof cookieValue === 'string') return cookieValue;if (isFunction(options.defaultValue)) {return options.defaultValue();}return options.defaultValue;});const updateState = useMemoizedFn((newValue: State | ((prevState: State) => State),newOptions: Cookies.CookieAttributes = {},) => {// options 是一开始初始化时候的 options,newOptions 是 update 的时候动态传入进来的// 把 defaultValue 剔除掉,然后用 ...restOptions 去接收剩余所有参数const { defaultValue, ...restOptions } = { ...options, ...newOptions };setState((prevState) => {const value = isFunction(newValue) ? newValue(prevState) : newValue;// 如果值是 undefined 的话,则是删除掉这个 cookieif (value === undefined) {Cookies.remove(cookieKey);} else {Cookies.set(cookieKey, value, restOptions);}return value;});},);return [state, updateState] as const;}
useLocalStorageState:将状态传入到 localStorage 里面
这里的 useLocalStorage 使用 createUseStorageState 包装了一层,兼容服务端渲染
const useLocalStorageState = createUseStorageState(() => (isBrowser ? localStorage : undefined));
options 可以设置 serializer 和 deserializer 自定义序列化与反序列化方法,在 hooks 内有这么一层函数封装:
// 如果定义了 serializer 方法,则优先调用 serializer 方法,否则默认是 JSON.stringifyconst serializer = (value: T) => {if (options?.serializer) {return options?.serializer(value);}return JSON.stringify(value);};// 如果定义了 deserializer 方法,则优先调用 deserializer 方法,否则默认是 JSON.parseconst deserializer = (value: string) => {if (options?.deserializer) {return options?.deserializer(value);}return JSON.parse(value);};
获取 state 方法:
function getStoredValue() {// 首先从 storage 获取值try {const raw = storage?.getItem(key);if (raw) {return deserializer(raw);}} catch (e) {console.error(e);}// 如果获取不到,则获取默认值if (isFunction<IFuncUpdater<T>>(options?.defaultValue)) {return options?.defaultValue();}return options?.defaultValue;}
更新 state 方法(这里的 else if 与 else 部分,应该还能做代码优化,合并成一个,判断下是不是 function,然后统一取一个值操作即可):
const updateState = (value?: T | IFuncUpdater<T>) => {// 如果设置的值 value 是 undefined 的话,就清空这个值if (typeof value === 'undefined') {setState(undefined);storage?.removeItem(key);} else if (isFunction<IFuncUpdater<T>>(value)) {// 这里是函数的情况const currentState = value(state);try {setState(currentState);storage?.setItem(key, serializer(currentState));} catch (e) {console.error(e);}} else {// 这里是值的情况try {setState(value);storage?.setItem(key, serializer(value));} catch (e) {console.error(e);}}};
state 的保存与写入:
// 第一次初始化时,获取 localStorage 对应 key 的值,并写入 stateconst [state, setState] = useState<T | undefined>(() => getStoredValue());// 忽略第一次加载,在当 key 发生变化时,重新获取获取 localStorage 对应 key 的值,并写入 stateuseUpdateEffect(() => {setState(getStoredValue());}, [key]);
useSessionStorageState:将状态传入 sessionStorage 里面
useDebounce:处理防抖【值】
基于 useDebounceFn 封装的 useDebounce,建议先阅读 Effect 篇的 useDebounceFn
针对传入 value 值的防抖,调用 useDebounceFn 返回防抖函数,value 改变时,触发防抖函数即可
import { useEffect, useState } from 'react';import useDebounceFn from '../useDebounceFn';import type { DebounceOptions } from './debounceOptions';function useDebounce<T>(value: T, options?: DebounceOptions) {const [debounced, setDebounced] = useState(value);const { run } = useDebounceFn(() => {setDebounced(value);}, options);useEffect(() => {run();}, [value]);return debounced;}
useThrottle:处理节流【值】
基于 useThrottleFn 封装的 useThrottle,建议先阅读 Effect 篇的 useThrottleFn
针对传入 value 值的防抖,调用 useThrottleFn 返回节流函数,value 改变时,触发节流函数即可
import { useEffect, useState } from 'react';import useThrottleFn from '../useThrottleFn';import type { ThrottleOptions } from './throttleOptions';function useThrottle<T>(value: T, options?: ThrottleOptions) {const [throttled, setThrottled] = useState(value);const { run } = useThrottleFn(() => {setThrottled(value);}, options);useEffect(() => {run();}, [value]);return throttled;}
useMap:管理 Map 状态
传入 initialValue 作为默认值,返回几个扩展方法:
- map:当前 map 值
- reset:重置为默认值
这两个可以一起看,封装了一个 getInitValue 方法,获取当前的 hooks 传进来的 initialValue,reset 的时候重新调用 getInitValue 获取值再 setMap 即可
const getInitValue = () => {return initialValue === undefined ? new Map() : new Map(initialValue);};const [map, setMap] = useState<Map<K, T>>(() => getInitValue());const reset = () => setMap(getInitValue());
set:添加元素,每次都是拿到上次的 map,重新 new 一个,再 set 新的 key-value
const set = (key: K, entry: T) => {setMap((prev) => {const temp = new Map(prev);temp.set(key, entry);return temp;});};
get:获取元素,直接调用 map 的 get 方法即可
const get = (key: K) => map.get(key);
setAll:生成新的 Map
const setAll = (newMap: Iterable<readonly [K, T]>) => {setMap(new Map(newMap));};
remove:移除某个元素
const remove = (key: K) => {setMap((prev) => {const temp = new Map(prev);temp.delete(key);return temp;});};
这里可以看到 useMap 的 initailValue 参数和 setAll 的 newMap 参数,类型都是 Iterable
,说明可以传入 new Array([x, x]) 或者 new Map([x, x]) 进行初始化或者重新设置参数,关于 Iterable 的说明:https://www.typescriptlang.org/docs/handbook/iterators-and-generators.html useSet:管理 Set 状态
传入 initialValue 作为默认值,返回几个扩展方法:
set:当前 set 值
- reset:重置默认值
这两个可以一起看,封装了一个 getInitValue 方法,获取当前的 hooks 传进来的 initialValue,reset 的时候重新调用 getInitValue 获取值再 setSet 即可
const getInitValue = () => {return initialValue === undefined ? new Set<K>() : new Set(initialValue);};const [set, setSet] = useState<Set<K>>(() => getInitValue());const reset = () => setSet(getInitValue());
add:添加元素,先判断 has 是否已经有该值,有的话则直接 return,没有则调用 set 的 add 即可
const add = (key: K) => {if (set.has(key)) {return;}setSet((prevSet) => {const temp = new Set(prevSet);temp.add(key);return temp;});};
remove:移除元素,先判断 has 是否存在该值,若没有则直接 return,有的话则调用 delete 删除即可
const remove = (key: K) => {if (!set.has(key)) {return;}setSet((prevSet) => {const temp = new Set(prevSet);temp.delete(key);return temp;});};
usePrevious:保存上一个状态
使用两个值分别保存当前值 curRef 和上一个值 prevRef
- 调用 shouldUpdate 查看是否需要更新 curRef 与 prevRef,如果不传入,默认是两值不一样时就触发更新
```javascript
export type ShouldUpdateFunc
= (prev: T | undefined, next: T) => boolean;
const defaultShouldUpdate =
function usePrevious
if (shouldUpdate(curRef.current, state)) { prevRef.current = curRef.current; curRef.current = state; }
return prevRef.current; }
<a name="yI5GD"></a>## useRafState:只在 requestAnimationFrame callback 时更新 state,用于性能优化[https://developer.mozilla.org/zh-CN/docs/Web/API/window/requestAnimationFrame](https://developer.mozilla.org/zh-CN/docs/Web/API/window/requestAnimationFrame)- requestAnimationFrame 是将所有的动画都放到一个浏览器重绘周期里去做,该方法接收一个回调函数,会在下一次浏览器重绘之前去执行- ref 记录事件监听函数,组件 unmount 的时候 cancelAnimationFrame 取消事件注册- 每次调用 setRafState 时,都把上次注册的 requestAnimationFrame 监听事件取消,再重新注册 requestAnimationFrame 回调,在回调函数内调用 setState```javascriptfunction useRafState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];function useRafState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];function useRafState<S>(initialState?: S | (() => S)) {const ref = useRef(0);const [state, setState] = useState(initialState);const setRafState = useCallback((value: S | ((prevState: S) => S)) => {cancelAnimationFrame(ref.current);ref.current = requestAnimationFrame(() => {setState(value);});}, []);useUnmount(() => {cancelAnimationFrame(ref.current);});return [state, setRafState] as const;}
useSafeState:组件卸载后,异步回调内的 setState 不再执行,避免因组件卸载后更新状态而导致的内存泄漏问题
- 使用 unmountedRef 判断当前组件是否已经销毁,如果是,则直接 return,不是则调用 setState 设置值 ```javascript import { useCallback, useState } from ‘react’; import type { Dispatch, SetStateAction } from ‘react’; import useUnmountedRef from ‘../useUnmountedRef’;
function useSafeState(initialState: S | (() => S)): [S, Dispatch
function useSafeState(): [S | undefined, Dispatch
function useSafeState(initialState?: S | (() => S)) {
const unmountedRef = useUnmountedRef();
const [state, setState] = useState(initialState);
const setCurrentState = useCallback((currentState) => {
/* if component is unmounted, stop update /
if (unmountedRef.current) return;
setState(currentState);
}, []);
return [state, setCurrentState] as const; }
- 例子中这里要注意的是,组件的确是销毁了,但是里面的异步操作的确也会执行到,只是说 setValue 是 set 不到的,比如这里还是会 console.log 出来的```javascriptconst Child = () => {const [value, setValue] = useSafeState<string>();useEffect(() => {setTimeout(() => {console.log(435432543252);setValue('data loaded from server');}, 5000);}, []);const text = value || 'Loading...';return <div>{text}</div>;};
useGetState:扩展 React.useState 方法,增加一个 getter,获取最新的值
使用 useRef 记录最新的值
type GetState<S> = () => S;function useGetState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>, GetState<S>] {const [state, setState] = useState<S>(initialState);const stateRef = useRef<S>(state);stateRef.current = state;const getState = useCallback<GetState<S>>(() => stateRef.current, []);return [state, setState, getState];}
可以查看例子,【快速】点击按钮 6 次:
- 按钮上的 count 值,从 1 - 6 逐一变化
- useEffect 内定义了计数器,每 3s 输出一个结果,可以看到,getCount 一直都是输出 6,1 - 5 的数值没有输出出来

