useSetState:管理 object 类型 state

主要原因是因为 useState 不会自动合并更新对象,大部分情况下需要我们自己手动合并,因此提供了 useSetState hooks 来解决这个问题
截屏2022-01-06 上午11.06.22.png

  1. const useSetState = <S extends Record<string, any>>(
  2. initialState: S | (() => S),
  3. ): [S, SetState<S>] => {
  4. const [state, setState] = useState<S>(initialState);
  5. const setMergeState = useCallback((patch) => {
  6. setState((prevState) => {
  7. // 判断是否为 function,若是则调用 function 获取状态
  8. const newState = isFunction(patch) ? patch(prevState) : patch;
  9. // 合并
  10. return newState ? { ...prevState, ...newState } : prevState;
  11. });
  12. }, []);
  13. return [state, setMergeState];
  14. };

useBoolean:管理 boolean 状态

  1. export default function useBoolean(defaultValue = false): [boolean, Actions] {
  2. const [state, { toggle, set }] = useToggle(defaultValue);
  3. // 函数不会变,使用 useMemo
  4. const actions: Actions = useMemo(() => {
  5. const setTrue = () => set(true); // 设置 true
  6. const setFalse = () => set(false); // 设置 false
  7. return {
  8. toggle, // 切换
  9. set: (v) => set(!!v), // 设置值
  10. setTrue,
  11. setFalse,
  12. };
  13. }, []);
  14. return [state, actions];
  15. }

useToggle:接收两个状态值,设置两个状态值来回切换

useToggle() 不传参数,则效果和 useBoolean 是一致的。

  1. export interface Actions<T> {
  2. setLeft: () => void;
  3. setRight: () => void;
  4. set: (value: T) => void;
  5. toggle: () => void;
  6. }
  7. function useToggle<T = boolean>(): [boolean, Actions<T>];
  8. function useToggle<T>(defaultValue: T): [T, Actions<T>];
  9. function useToggle<T, U>(defaultValue: T, reverseValue: U): [T | U, Actions<T | U>];
  10. function useToggle<D, R>(defaultValue: D = false as unknown as D, reverseValue?: R) {
  11. const [state, setState] = useState<D | R>(defaultValue);
  12. const actions = useMemo(() => {
  13. const reverseValueOrigin = (reverseValue === undefined ? !defaultValue : reverseValue) as D | R;
  14. // 切换,跟上一个值判断是不是一样,然后切另一个
  15. const toggle = () => setState((s) => (s === defaultValue ? reverseValueOrigin : defaultValue));
  16. const set = (value: D | R) => setState(value); // 设置值
  17. const setLeft = () => setState(defaultValue); // 设置左值
  18. const setRight = () => setState(reverseValueOrigin); // 设置右值
  19. return {
  20. toggle,
  21. set,
  22. setLeft,
  23. setRight,
  24. };
  25. // useToggle ignore value change
  26. // }, [defaultValue, reverseValue]);
  27. }, []);
  28. return [state, actions];
  29. }

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) => { const newQuery = typeof s === ‘function’ ? s(targetQuery) : s;

  1. // 1. 如果 setState 后,search 没变化,就需要 update 来触发一次更新。比如 demo1 直接点击 clear,就需要 update 来触发更新。
  2. // 2. update 和 history 的更新会合并,不会造成多次更新
  3. update();
  4. // 主要兼容了两个版本 react-router 的 api,一个是 history,一个是 navigate
  5. if (history) {
  6. history[navigateMode]({
  7. hash: location.hash,
  8. search: stringify({ ...queryFromUrl, ...newQuery }, parseConfig) || '?',
  9. });
  10. }
  11. if (navigate) {
  12. navigate(
  13. {
  14. hash: location.hash,
  15. search: stringify({ ...queryFromUrl, ...newQuery }, parseConfig) || '?',
  16. },
  17. {
  18. replace: navigateMode === 'replace',
  19. },
  20. );
  21. }

};

return [targetQuery, useMemoizedFn(setState)] as const; };

  1. <a name="UsPUK"></a>
  2. ## useCookieState:存储 cookie
  3. - cookies 主要使用 js-cookie:[https://github.com/js-cookie/js-cookie](https://github.com/js-cookie/js-cookie)
  4. - useCookieState 的时候可以传入 js-cookie 的 option,在 updateState 的时候也可以传入,会对两者进行合并
  5. ```javascript
  6. export type State = string | undefined;
  7. // 基于 cookies 的 options 再扩展多一个属性 defaultValue
  8. export interface Options extends Cookies.CookieAttributes {
  9. defaultValue?: State | (() => State);
  10. }
  11. // 传入 cookieKey,还有 js-cookie 的 options
  12. function useCookieState(cookieKey: string, options: Options = {}) {
  13. const [state, setState] = useState<State>(() => {
  14. const cookieValue = Cookies.get(cookieKey);
  15. if (typeof cookieValue === 'string') return cookieValue;
  16. if (isFunction(options.defaultValue)) {
  17. return options.defaultValue();
  18. }
  19. return options.defaultValue;
  20. });
  21. const updateState = useMemoizedFn(
  22. (
  23. newValue: State | ((prevState: State) => State),
  24. newOptions: Cookies.CookieAttributes = {},
  25. ) => {
  26. // options 是一开始初始化时候的 options,newOptions 是 update 的时候动态传入进来的
  27. // 把 defaultValue 剔除掉,然后用 ...restOptions 去接收剩余所有参数
  28. const { defaultValue, ...restOptions } = { ...options, ...newOptions };
  29. setState((prevState) => {
  30. const value = isFunction(newValue) ? newValue(prevState) : newValue;
  31. // 如果值是 undefined 的话,则是删除掉这个 cookie
  32. if (value === undefined) {
  33. Cookies.remove(cookieKey);
  34. } else {
  35. Cookies.set(cookieKey, value, restOptions);
  36. }
  37. return value;
  38. });
  39. },
  40. );
  41. return [state, updateState] as const;
  42. }

useLocalStorageState:将状态传入到 localStorage 里面

这里的 useLocalStorage 使用 createUseStorageState 包装了一层,兼容服务端渲染

  1. const useLocalStorageState = createUseStorageState(() => (isBrowser ? localStorage : undefined));

options 可以设置 serializer 和 deserializer 自定义序列化与反序列化方法,在 hooks 内有这么一层函数封装:

  1. // 如果定义了 serializer 方法,则优先调用 serializer 方法,否则默认是 JSON.stringify
  2. const serializer = (value: T) => {
  3. if (options?.serializer) {
  4. return options?.serializer(value);
  5. }
  6. return JSON.stringify(value);
  7. };
  8. // 如果定义了 deserializer 方法,则优先调用 deserializer 方法,否则默认是 JSON.parse
  9. const deserializer = (value: string) => {
  10. if (options?.deserializer) {
  11. return options?.deserializer(value);
  12. }
  13. return JSON.parse(value);
  14. };

获取 state 方法:

  1. function getStoredValue() {
  2. // 首先从 storage 获取值
  3. try {
  4. const raw = storage?.getItem(key);
  5. if (raw) {
  6. return deserializer(raw);
  7. }
  8. } catch (e) {
  9. console.error(e);
  10. }
  11. // 如果获取不到,则获取默认值
  12. if (isFunction<IFuncUpdater<T>>(options?.defaultValue)) {
  13. return options?.defaultValue();
  14. }
  15. return options?.defaultValue;
  16. }

更新 state 方法(这里的 else if 与 else 部分,应该还能做代码优化,合并成一个,判断下是不是 function,然后统一取一个值操作即可):

  1. const updateState = (value?: T | IFuncUpdater<T>) => {
  2. // 如果设置的值 value 是 undefined 的话,就清空这个值
  3. if (typeof value === 'undefined') {
  4. setState(undefined);
  5. storage?.removeItem(key);
  6. } else if (isFunction<IFuncUpdater<T>>(value)) {
  7. // 这里是函数的情况
  8. const currentState = value(state);
  9. try {
  10. setState(currentState);
  11. storage?.setItem(key, serializer(currentState));
  12. } catch (e) {
  13. console.error(e);
  14. }
  15. } else {
  16. // 这里是值的情况
  17. try {
  18. setState(value);
  19. storage?.setItem(key, serializer(value));
  20. } catch (e) {
  21. console.error(e);
  22. }
  23. }
  24. };

state 的保存与写入:

  1. // 第一次初始化时,获取 localStorage 对应 key 的值,并写入 state
  2. const [state, setState] = useState<T | undefined>(() => getStoredValue());
  3. // 忽略第一次加载,在当 key 发生变化时,重新获取获取 localStorage 对应 key 的值,并写入 state
  4. useUpdateEffect(() => {
  5. setState(getStoredValue());
  6. }, [key]);

useSessionStorageState:将状态传入 sessionStorage 里面

与 useLocalStorageState 一致

useDebounce:处理防抖【值】

基于 useDebounceFn 封装的 useDebounce,建议先阅读 Effect 篇的 useDebounceFn
针对传入 value 值的防抖,调用 useDebounceFn 返回防抖函数,value 改变时,触发防抖函数即可

  1. import { useEffect, useState } from 'react';
  2. import useDebounceFn from '../useDebounceFn';
  3. import type { DebounceOptions } from './debounceOptions';
  4. function useDebounce<T>(value: T, options?: DebounceOptions) {
  5. const [debounced, setDebounced] = useState(value);
  6. const { run } = useDebounceFn(() => {
  7. setDebounced(value);
  8. }, options);
  9. useEffect(() => {
  10. run();
  11. }, [value]);
  12. return debounced;
  13. }

useThrottle:处理节流【值】

基于 useThrottleFn 封装的 useThrottle,建议先阅读 Effect 篇的 useThrottleFn
针对传入 value 值的防抖,调用 useThrottleFn 返回节流函数,value 改变时,触发节流函数即可

  1. import { useEffect, useState } from 'react';
  2. import useThrottleFn from '../useThrottleFn';
  3. import type { ThrottleOptions } from './throttleOptions';
  4. function useThrottle<T>(value: T, options?: ThrottleOptions) {
  5. const [throttled, setThrottled] = useState(value);
  6. const { run } = useThrottleFn(() => {
  7. setThrottled(value);
  8. }, options);
  9. useEffect(() => {
  10. run();
  11. }, [value]);
  12. return throttled;
  13. }

useMap:管理 Map 状态

传入 initialValue 作为默认值,返回几个扩展方法:

  • map:当前 map 值
  • reset:重置为默认值

这两个可以一起看,封装了一个 getInitValue 方法,获取当前的 hooks 传进来的 initialValue,reset 的时候重新调用 getInitValue 获取值再 setMap 即可

  1. const getInitValue = () => {
  2. return initialValue === undefined ? new Map() : new Map(initialValue);
  3. };
  4. const [map, setMap] = useState<Map<K, T>>(() => getInitValue());
  5. const reset = () => setMap(getInitValue());
  • set:添加元素,每次都是拿到上次的 map,重新 new 一个,再 set 新的 key-value

    1. const set = (key: K, entry: T) => {
    2. setMap((prev) => {
    3. const temp = new Map(prev);
    4. temp.set(key, entry);
    5. return temp;
    6. });
    7. };
  • get:获取元素,直接调用 map 的 get 方法即可

    1. const get = (key: K) => map.get(key);
  • setAll:生成新的 Map

    1. const setAll = (newMap: Iterable<readonly [K, T]>) => {
    2. setMap(new Map(newMap));
    3. };
  • remove:移除某个元素

    1. const remove = (key: K) => {
    2. setMap((prev) => {
    3. const temp = new Map(prev);
    4. temp.delete(key);
    5. return temp;
    6. });
    7. };

    这里可以看到 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 即可

  1. const getInitValue = () => {
  2. return initialValue === undefined ? new Set<K>() : new Set(initialValue);
  3. };
  4. const [set, setSet] = useState<Set<K>>(() => getInitValue());
  5. const reset = () => setSet(getInitValue());
  • add:添加元素,先判断 has 是否已经有该值,有的话则直接 return,没有则调用 set 的 add 即可

    1. const add = (key: K) => {
    2. if (set.has(key)) {
    3. return;
    4. }
    5. setSet((prevSet) => {
    6. const temp = new Set(prevSet);
    7. temp.add(key);
    8. return temp;
    9. });
    10. };
  • remove:移除元素,先判断 has 是否存在该值,若没有则直接 return,有的话则调用 delete 删除即可

    1. const remove = (key: K) => {
    2. if (!set.has(key)) {
    3. return;
    4. }
    5. setSet((prevSet) => {
    6. const temp = new Set(prevSet);
    7. temp.delete(key);
    8. return temp;
    9. });
    10. };

    usePrevious:保存上一个状态

  • 使用两个值分别保存当前值 curRef 和上一个值 prevRef

  • 调用 shouldUpdate 查看是否需要更新 curRef 与 prevRef,如果不传入,默认是两值不一样时就触发更新 ```javascript export type ShouldUpdateFunc = (prev: T | undefined, next: T) => boolean;

const defaultShouldUpdate = (a?: T, b?: T) => a !== b;

function usePrevious( state: T, shouldUpdate: ShouldUpdateFunc = defaultShouldUpdate, ): T | undefined { const prevRef = useRef(); const curRef = useRef();

if (shouldUpdate(curRef.current, state)) { prevRef.current = curRef.current; curRef.current = state; }

return prevRef.current; }

  1. <a name="yI5GD"></a>
  2. ## useRafState:只在 requestAnimationFrame callback 时更新 state,用于性能优化
  3. [https://developer.mozilla.org/zh-CN/docs/Web/API/window/requestAnimationFrame](https://developer.mozilla.org/zh-CN/docs/Web/API/window/requestAnimationFrame)
  4. - requestAnimationFrame 是将所有的动画都放到一个浏览器重绘周期里去做,该方法接收一个回调函数,会在下一次浏览器重绘之前去执行
  5. ![截屏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=)
  6. - ref 记录事件监听函数,组件 unmount 的时候 cancelAnimationFrame 取消事件注册
  7. - 每次调用 setRafState 时,都把上次注册的 requestAnimationFrame 监听事件取消,再重新注册 requestAnimationFrame 回调,在回调函数内调用 setState
  8. ```javascript
  9. function useRafState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
  10. function useRafState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];
  11. function useRafState<S>(initialState?: S | (() => S)) {
  12. const ref = useRef(0);
  13. const [state, setState] = useState(initialState);
  14. const setRafState = useCallback((value: S | ((prevState: S) => S)) => {
  15. cancelAnimationFrame(ref.current);
  16. ref.current = requestAnimationFrame(() => {
  17. setState(value);
  18. });
  19. }, []);
  20. useUnmount(() => {
  21. cancelAnimationFrame(ref.current);
  22. });
  23. return [state, setRafState] as const;
  24. }

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; }

  1. - 例子中这里要注意的是,组件的确是销毁了,但是里面的异步操作的确也会执行到,只是说 setValue set 不到的,比如这里还是会 console.log 出来的
  2. ```javascript
  3. const Child = () => {
  4. const [value, setValue] = useSafeState<string>();
  5. useEffect(() => {
  6. setTimeout(() => {
  7. console.log(435432543252);
  8. setValue('data loaded from server');
  9. }, 5000);
  10. }, []);
  11. const text = value || 'Loading...';
  12. return <div>{text}</div>;
  13. };

useGetState:扩展 React.useState 方法,增加一个 getter,获取最新的值

使用 useRef 记录最新的值

  1. type GetState<S> = () => S;
  2. function useGetState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>, GetState<S>] {
  3. const [state, setState] = useState<S>(initialState);
  4. const stateRef = useRef<S>(state);
  5. stateRef.current = state;
  6. const getState = useCallback<GetState<S>>(() => stateRef.current, []);
  7. return [state, setState, getState];
  8. }

可以查看例子,【快速】点击按钮 6 次:

  • 按钮上的 count 值,从 1 - 6 逐一变化
  • useEffect 内定义了计数器,每 3s 输出一个结果,可以看到,getCount 一直都是输出 6,1 - 5 的数值没有输出出来

截屏2022-01-06 下午7.02.17.png