- 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);
// 函数不会变,使用 useMemo
const actions: Actions = useMemo(() => {
const setTrue = () => set(true); // 设置 true
const setFalse = () => set(false); // 设置 false
return {
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,一个是 navigate
if (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 的时候也可以传入,会对两者进行合并
```javascript
export type State = string | undefined;
// 基于 cookies 的 options 再扩展多一个属性 defaultValue
export interface Options extends Cookies.CookieAttributes {
defaultValue?: State | (() => State);
}
// 传入 cookieKey,还有 js-cookie 的 options
function 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 的话,则是删除掉这个 cookie
if (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.stringify
const serializer = (value: T) => {
if (options?.serializer) {
return options?.serializer(value);
}
return JSON.stringify(value);
};
// 如果定义了 deserializer 方法,则优先调用 deserializer 方法,否则默认是 JSON.parse
const 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 的值,并写入 state
const [state, setState] = useState<T | undefined>(() => getStoredValue());
// 忽略第一次加载,在当 key 发生变化时,重新获取获取 localStorage 对应 key 的值,并写入 state
useUpdateEffect(() => {
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 是将所有的动画都放到一个浏览器重绘周期里去做,该方法接收一个回调函数,会在下一次浏览器重绘之前去执行
![截屏2022-01-06 下午8.32.14.png](https://cdn.nlark.com/yuque/0/2022/png/22895623/1641472431898-00a32c19-8977-4cfb-bc1c-a38626ddb7ff.png#clientId=u3351545b-06be-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=ua91705cd&margin=%5Bobject%20Object%5D&name=%E6%88%AA%E5%B1%8F2022-01-06%20%E4%B8%8B%E5%8D%888.32.14.png&originHeight=504&originWidth=981&originalType=binary&ratio=1&rotation=0&showTitle=false&size=168666&status=done&style=none&taskId=uac6fad47-74ed-4cd0-aee9-5d66026367b&title=)
- ref 记录事件监听函数,组件 unmount 的时候 cancelAnimationFrame 取消事件注册
- 每次调用 setRafState 时,都把上次注册的 requestAnimationFrame 监听事件取消,再重新注册 requestAnimationFrame 回调,在回调函数内调用 setState
```javascript
function 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 出来的
```javascript
const 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 的数值没有输出出来