ahooks - Sophiekx/Sophiekx.github.io GitHub Wiki

feature:基于Ts、支持 SSR、无闭包问题、丰富的基础&高级 Hooks。求场生态当作高调

API

// request
useRequest:异步数据管理的 Hooks,通过插件式组织代码。
已有能力包括:自动请求/手动请求、轮询、防抖、节流、屏幕聚焦重新请求、错误重试、loading delay、SWR、缓存

 
// Scene
useAntdTable:基于 useRequest 实现,封装了常用的 Ant Design Form 与 Ant Design Table 联动逻辑,并且同时支持 antd v3 和 v4。
useFusionTable:基于 useRequest 实现,封装了常用的 Fusion Form 与 Fusion Table 联动逻辑。
useInfiniteScroll:封装了常见的无限滚动逻辑。基于 useRequest 实现,封装了常见的分页逻辑。
usePagination:基于 useRequest 实现,封装了常见的分页逻辑。
useDynamicList:管理动态列表状态,并能生成唯一 key 的 Hook。
useVirtualList:提供虚拟化列表能力的 Hook,用于解决展示海量数据渲染时首屏渲染缓慢和滚动卡顿问题。
useHistoryTravel:管理状态历史变化记录,方便在历史记录中前进与后退。
useNetwork:管理网络连接状态的 Hook。
useSelections:常见联动 Checkbox 逻辑封装,支持多选,单选,全选逻辑,还提供了是否选择,是否全选,是否半选的状态。
useCountDown:用于管理倒计时的 Hook。
useCounter:管理计数器的 Hook。
useTextSelection:实时获取用户当前选取的文本内容及位置。
useWebSocket:用于处理 WebSocket 的 Hook。
 
// LifeCycle
useMount:只在组件初始化时执行的 Hook。
useUnmount:在组件卸载(unmount)时执行的 Hook。
useUnmountedRef:获取当前组件是否已经卸载的 Hook。
 
// State
useSetState:管理 object 类型 state 的 Hooks,用法与 class 组件的 this.setState 基本一致。
useBoolean:管理 boolean 状态的 Hook。
useToggle:用于在两个状态值间切换的 Hook。
useUrlState:通过 url query 来管理 state 的 Hook。
useCookieState:将状态存储在 Cookie 中的 Hook 。
useLocalStorageState:将状态存储在 localStorage 中的 Hook。
useSessionStorageState:将状态存储在 sessionStorage 中的 Hook。
useDebounce:用来处理防抖值的 Hook。
useThrottle:用来处理节流值的 Hook。
useMap:管理 Map 类型状态的 Hook。
useSet:管理 Set 类型状态的 Hook。
usePrevious:保存上一次状态的 Hook。
useRafState:只在 requestAnimationFrame callback 时更新 state,一般用于性能优化。
useSafeState:用法与 React.useState 完全一样,但是在组件卸载后异步回调内的 setState 不再执行,避免因组件卸载后更新状态而导致的内存泄漏。
useGetState:给 React.useState 增加了一个 getter 方法,以获取当前最新值。
useResetState:提供重置 state 方法的 Hooks,用法与 React.useState 基本一致。

// Effect
useUpdateEffect:用法等同于 useEffect,但是会忽略首次执行,只在依赖更新时执行。
useUpdateLayoutEffect:用法等同于 useLayoutEffect,但是会忽略首次执行,只在依赖更新时执行。
useAsyncEffect:useEffect 支持异步函数。
useDebounceEffect:为 useEffect 增加防抖的能力。
useDebounceFn:用来处理防抖函数的 Hook。
useThrottleFn:用来处理函数节流的 Hook。
useThrottleEffect:为 useEffect 增加节流的能力。
useDeepCompareEffect:用法与 useEffect 一致,但 deps 通过 react-fast-compare 进行深比较。
useDeepCompareLayoutEffect:用法与 useLayoutEffect 一致,但 deps 通过 react-fast-compare 进行深比较。
useInterval:可以处理 setInterval 的 Hook。
useRafInterval:用 requestAnimationFrame 模拟实现 setInterval,API 和 useInterval 保持一致,好处是可以在页面不渲染的时候停止执行定时器,比如页面隐藏或最小化等。
useTimeout:可以处理 setTimeout 计时器函数的 Hook。
useRafTimeout:用 requestAnimationFrame 模拟实现 setTimeout,API 和 useTimeout 保持一致,好处是可以在页面不渲染的时候不触发函数执行,比如页面隐藏或最小化等。
useLockFn:用于给一个异步函数增加竞态锁,防止并发执行。
useUpdate:返回一个函数,调用该函数会强制组件重新渲染。

// Dom
useEventListener:优雅的使用 addEventListener。
useClickAway:监听目标元素外的点击事件。
useDocumentVisibility:监听页面是否可见,参考 visibilityState API
useDrop & useDrag:处理元素拖拽的 Hook。
useEventTarget:常见表单控件(通过 e.target.value 获取表单值) 的 onChange 跟 value 逻辑封装,支持自定义值转换和重置功能。
useExternal:动态注入 JS 或 CSS 资源,useExternal 可以保证资源全局唯一。
useTitle:用于设置页面标题。
useFavicon:设置页面的 favicon。
useFullscreen:管理 DOM 全屏的 Hook。
useHover:监听 DOM 元素是否有鼠标悬停。
useMutationObserver:一个监听指定的 DOM 树发生变化的 Hook,参考 MutationObserver
useInViewport:观察元素是否在可见区域,以及元素可见比例。更多信息参考 Intersection Observer API。
useKeyPress:监听键盘按键,支持组合键,支持按键别名。
useLongPress:监听目标元素的长按事件。
useMouse:监听鼠标位置。
useResponsive:获取响应式信息。
useScroll:监听元素的滚动位置。
useSize:监听 DOM 节点尺寸变化的 Hook。
useFocusWithin:监听当前焦点是否在某个区域之内,同 css 属性 :focus-within。

// Advanced
useControllableValue:组件的状态既可以自己管理,也可以被外部控制。
useCreation:useMemo 或 useRef 的替代品。
useEventEmitter:在多个组件之间进行事件通知。
useIsomorphicLayoutEffect:在 SSR 模式下,使用 useLayoutEffect 时,会出现以下警告,为了避免该警告,可以使用 useIsomorphicLayoutEffect 代替 useLayoutEffect。
useLatest:返回当前最新值的 Hook,可以避免闭包问题。
useMemoizedFn:持久化 function 的 Hook,一般情况下,可以使用 useMemoizedFn 完全代替 useCallback。
useReactive:提供一种数据响应式的操作体验,定义数据状态不需要写useState,直接修改属性即可刷新视图。

// Dev
useTrackedEffect:追踪是哪个依赖变化触发了 useEffect 的执行。
useWhyDidYouUpdate:帮助开发者排查是哪个属性改变导致了组件的 rerender。

Usage

🌰const { data, error, loading } = useRequest(service, options?, plugins?);
{
  manual?: boolean;

  onBefore?: (params: TParams) => void;
  onSuccess?: (data: TData, params: TParams) => void;
  onError?: (e: Error, params: TParams) => void;
  // formatResult?: (res: any) => TData;
  onFinally?: (params: TParams, data?: TData, e?: Error) => void;

  defaultParams?: TParams;

  // refreshDeps
  refreshDeps?: DependencyList;
  refreshDepsAction?: () => void;

  // loading delay
  loadingDelay?: number;

  // polling
  pollingInterval?: number;
  pollingWhenHidden?: boolean;
  pollingErrorRetryCount?: number;

  // refresh on window focus
  refreshOnWindowFocus?: boolean;
  focusTimespan?: number;

  // debounce
  debounceWait?: number;
  debounceLeading?: boolean;
  debounceTrailing?: boolean;
  debounceMaxWait?: number;

  // throttle
  throttleWait?: number;
  throttleLeading?: boolean;
  throttleTrailing?: boolean;

  // cache
  cacheKey?: string;
  cacheTime?: number;
  staleTime?: number;
  setCache?: (data: CachedData<TData, TParams>) => void;
  getCache?: (params: TParams) => CachedData<TData, TParams> | undefined;

  // retry
  retryCount?: number;
  retryInterval?: number;

  // ready
  ready?: boolean;

  // [key: string]: any;
}


🌰const { search, tableProps, ... } = useAntdTable(service, options)
AntdTableOptions extends PaginationOptions<TData, TParams> {
  form?: AntdFormUtils;
  defaultType?: 'simple' | 'advance';
}


🌰const { search, tableProps, paginationProps, ... } = useFusionTable(service, options)
FusionTableOptions extends extends Omit<AntdTableOptions<TData, TParams>, 'form'> {
  field?: Field;
}


🌰 const { data, loading, loadingMore, error?, noMore, loadMore, loadMoreAsync, reload, reloadAsync, cancel, mutate, } = useInfiniteScroll<TData extends Data>(
  service: (currentData?: TData) => Promise<TData>,
  {
    target?: BasicTarget<Element | Document>;
    isNoMore?: (data?: TData) => boolean;
    threshold?: number;

    manual?: boolean;
    reloadDeps?: DependencyList;

    onBefore?: () => void;
    onSuccess?: (data: TData) => void;
    onError?: (e: Error) => void;
    onFinally?: (data?: TData, e?: Error) => void;
    }
);


🌰 const {
  ...,
  pagination: {
    current: number;
    pageSize: number;
    total: number;
    totalPage: number;
    onChange: (current: number, pageSize: number) => void;
    changeCurrent: (current: number) => void;
    changePageSize: (pageSize: number) => void;
  }
} = usePagination<TData extends Data, TParams extends Params>(
  service: (...args: TParams) => Promise<TData>,
  {
    ...,
    defaultPageSize?: number;
    defaultCurrent?: number;
    refreshDeps?: any[];
  }
);


🌰 const result: Result = useDynamicList(initialList?: T[]);


🌰 const [list, scrollTo] = useVirtualList<T>(
  originalList: T[],
  options: {
    containerTarget: (() => Element) | Element | MutableRefObject<Element>,
    wrapperTarget: (() => Element) | Element | MutableRefObject<Element>,
    itemHeight: number | ((index: number, data: T) => number),
    overscan?: number,
  }
);


🌰 const {
  value,
  setValue,
  backLength,
  forwardLength,
  go,
  back,
  forward
} = useHistoryTravel<T>(initialValue?: T, maxLength: number = 0);


🌰 const result: NetworkState = useNetwork();
interface NetworkState {
  online?: boolean;
  since?: Date;
  rtt?: number;
  type?: string;
  downlink?: number;
  saveData?: boolean;
  downlinkMax?: number;
  effectiveType?: string;
}


🌰 const result: Result = useSelections<T>(items: T[], options?: Options<T>);
interface Options<T> {
  defaultSelected?: T[];
  itemKey?: string | ((item: T) => Key);
}


🌰 const [countdown, formattedRes] = useCountDown(
  {
    leftTime,
    targetDate,
    interval,
    onEnd
  }
);
interface FormattedRes {
  days: number;
  hours: number;
  minutes: number;
  seconds: number;
  milliseconds: number;
}


🌰const [current, {
  inc,
  dec,
  set,
  reset
}] = useCounter(initialValue, { min, max });


🌰const state = useTextSelection(target?);
State: {
  text: string;
  top: NaN,
  left: NaN,
  bottom: NaN,
  right: NaN,
  height: NaN,
  width: NaN,
}


🌰useWebSocket(socketUrl: string, options?: Options): Result;
interface Result {
  latestMessage?: WebSocketEventMap['message'];
  sendMessage: WebSocket['send'];
  disconnect: () => void;
  connect: () => void;
  readyState: ReadyState;
  webSocketIns?: WebSocket;
}


🌰useMount(fn: () => void);


🌰useUnmount(fn: () => void);


🌰const unmountRef: { current: boolean } = useUnmountedRef();


🌰const [state, setState] = useSetState<T>(initialState);


🌰const [state, { toggle, set, setTrue, setFalse }] = useBoolean(
  defaultValue?: boolean,
);


🌰const [state, { toggle, set, setLeft, setRight }] = useToggle<T, U>(defaultValue: T, reverseValue: U);


🌰const [state, setState] = useUrlState(initialState, options);


🌰const [state, setState]: [State, SetState] = useCookieState(
  cookieKey: string,
  options?: Options,
);
type State = string | undefined;
type SetState = (
  newValue?: State | ((prevState?: State) => State),
  options?: Cookies.CookieAttributes,
) => void;


🌰const [state, setState] = useLocalStorageState<T>(
  key: string,
  options: Options<T>
): [T?, (value?: SetState<T>) => void];
type SetState<S> = S | ((prevState?: S) => S);
interface Options<T> {
  defaultValue?: T | (() => T);
  serializer?: (value: T) => string;
  deserializer?: (value: string) => T;
  onError?: (error: unknown) => void;
}


🌰useSessionStorageState 同 useLocalStorageState


🌰const debouncedValue = useDebounce(
  value: any,
  options?: Options
);


🌰const throttledValue = useThrottle(
  value: any,
  options?: Options
);


🌰const [
  map,
  {
    set,
    setAll,
    remove,
    reset,
    get
  }
] = useMap<K, V>(initialValue);


🌰const [
  set,
  {
    add,
    remove,
    reset
  }
] = useSet<K>(initialValue);


🌰const previousState: T = usePrevious<T>(
  state: T,
  shouldUpdate?: (prev: T | undefined, next: T) => boolean
);


🌰useRafState:与 `React.useState` 一致


🌰const [state, setState] = useSafeState(initialState);


🌰const [state, setState, getState] = useGetState<S>(initialState)


🌰const [state, setState, resetState] = useResetState<S>(
  initialState: S | (() => S),
): [S, Dispatch<SetStateAction<S>>, () => void]


🌰useUpdateEffect(
  effect: React.EffectCallback,
  deps?: React.DependencyList,
)


🌰useUpdateLayoutEffect(
  effect: React.EffectCallback,
  deps?: React.DependencyList,
)


🌰function useAsyncEffect(
  effect: () => AsyncGenerator | Promise,
  deps: DependencyList
);


🌰useDebounceEffect(
  effect: EffectCallback,
  deps?: DependencyList,
  options?: Options
);


🌰const {
  run,
  cancel,
  flush
} = useDebounceFn(
  fn: (...args: any[]) => any,
  options?: Options
);


🌰const {
  run,
  cancel,
  flush
} = useThrottleFn(
  fn: (...args: any[]) => any,
  options?: Options
);


🌰useThrottleEffect(
  effect: EffectCallback,
  deps?: DependencyList,
  options?: Options
);


🌰useDeepCompareEffect(
  effect: React.EffectCallback,
  deps: React.DependencyList
);


🌰useDeepCompareLayoutEffect(
  effect: React.EffectCallback,
  deps: React.DependencyList
);


🌰useInterval(
  fn: () => void,
  delay?: number | undefined,
  options?: Options
): fn: () => void;


🌰useRafInterval(
  fn: () => void,
  delay?: number | undefined,
  options?: Options
): fn: () => void;


🌰useTimeout(
  fn: () => void,
  delay?: number | undefined
): fn: () => void;


🌰useRafTimeout(
  fn: () => void,
  delay?: number | undefined,
): fn: () => void;


function useLockFn<P extends any[] = any[], V = any>(
  fn: (...args: P) => Promise<V>
): fn: (...args: P) => Promise<V | undefined>;


🌰const update = useUpdate();

 
useEventListener(
  eventName: string,
  handler: (ev: Event) => void,
  options?: Options,
);


useClickAway<T extends Event = Event>(
  onClickAway: (event: T) => void,
  target: Target | Target[],
  eventName?: DocumentEventKey | DocumentEventKey[]
);


🌰const documentVisibility = useDocumentVisibility();


useDrag<T>(
  data: any,
  target: (() => Element) | Element | MutableRefObject<Element>,
  options?: DragOptions
);
useDrop<T>(
  target: (() => Element) | Element | MutableRefObject<Element>,
  options?: DropOptions
);


🌰const [value, { onChange, reset }] = useEventTarget<T, U>(Options<T, U>);


🌰const status = useExternal(path: string, options?: Options);


🌰useTitle(title: string, options?: Options);


🌰useFavicon(href: string);


🌰const [isFullscreen, {
  enterFullscreen,
  exitFullscreen,
  toggleFullscreen,
  isEnabled,
}] = useFullScreen(
  target,
  options?: Options
);


🌰const isHovering = useHover(
  target,
  {
   onEnter,
   onLeave,
   onChange
  }
);


🌰useMutationObserver(
  callback: MutationCallback,
  target: Target,
  options?: MutationObserverInit,
);


🌰const [inViewport, ratio] = useInViewport(
  target: Target | Target[],
  options?: Options
);


🌰useKeyPress(
  keyFilter: KeyFilter,
  eventHandler: (event: KeyboardEvent, key: KeyType) => void,
  options?: Options
);


🌰useLongPress(
  onLongPress: (event: MouseEvent | TouchEvent) => void,
  target: Target,
  options: {
    delay?: number;
    moveThreshold?: { x?: number; y?: number };
    onClick?: (event: MouseEvent | TouchEvent) => void;
    onLongPressEnd?: (event: MouseEvent | TouchEvent) => void;
  }
);


🌰const state: {
  screenX: number,
  screenY: number,
  clientX: number,
  clientY: number,
  pageX: number,
  pageY: number,
  elementX: number,
  elementY: number,
  elementH: number,
  elementW: number,
  elementPosX: number,
  elementPosY: number,
} = useMouse(target?: Target);


🌰function useResponsive(): ResponsiveInfo;
interface ResponsiveConfig {
  [key: string]: number;
}
interface ResponsiveInfo {
  [key: string]: boolean;
}
function configResponsive(config: ResponsiveConfig): void;


🌰const position = useScroll(target, shouldUpdate);


🌰const size = useSize(target);


🌰const isFocusWithin = useFocusWithin(
  target,
  {
   onFocus,
   onBlur,
   onChange
  }
);

 
🌰const [state, setState] = useControllableValue(props: Record<string, any>, options?: Options);


🌰useCreation(factory: () => T, deps: any[]): T;


🌰const result: Result = useEventEmitter<T>();


🌰useIsomorphicLayoutEffect:For SSR,同 useLayoutEffect


🌰const latestValueRef = useLatest<T>(value: T): MutableRefObject<T>;


🌰const memoizedFn = useMemoizedFn<T>(fn: T): T;


🌰const state = useReactive(initialState: Record<string, any>);

 
🌰useTrackedEffect(
  effect: (changes: [], previousDeps: [], currentDeps: []) => (void | (() => void | undefined)),
  deps?: deps,
)


🌰useWhyDidYouUpdate(componentName: string, props: IProps): void;
打开控制台,可以看到改变的属性。

原理

useRequest

useRequest (加载内外插件)=> useRequestImplement => useLatest(service)+useUpdate(/*内部更新时通知外部*/)+ new Fetch => User API + 内置插件机制
判断是否是浏览器环境: !!(typeof window !== 'undefined' && window.document && window.document.createElement);
判断页面是否可见: 浏览器环境 && document.visibilityState !== 'hidden';
判断页面是否在线: 浏览器环境 && navigator.onLine;
页面聚焦后的执行逻辑:
  window.addEventListener('visibilitychange', myLoginc, false);
  window.addEventListener('focus', myLoginc, false);
页面重新可见后的执行逻辑:window.addEventListener('visibilitychange', myLoginc, false);

useCountdown

  const { leftTime, targetDate, interval = 1000, onEnd } = options || {};
  const memoLeftTime = useMemo<TDate>(() => {
    return isNumber(leftTime) && leftTime > 0 ? Date.now() + leftTime : undefined;
  }, [leftTime]);
  const target = 'leftTime' in options ? memoLeftTime : targetDate;
  const [timeLeft, setTimeLeft] = useState(() => calcLeft(target));
  const onEndRef = useLatest(onEnd);
  useEffect(() => {
    if (!target) return setTimeLeft(0);
    setTimeLeft(calcLeft(target));
    const timer = setInterval(() => {
      const targetLeft = calcLeft(target);
      setTimeLeft(targetLeft);
      if (targetLeft === 0) {
        clearInterval(timer);
        onEndRef.current?.();
      }
    }, interval);
    return () => clearInterval(timer);
  }, [target, interval]);
  const formattedRes = useMemo(() => parseMs(timeLeft), [timeLeft]);
  return [timeLeft, formattedRes] as const;

useCounter

  const { min, max } = options;
  const [current, setCurrent] = useState(() => {
    return getTargetValue(initialValue, { min, max, });
  });
  const setValue = (value: ValueParam) => {
    setCurrent((c) => {
      const target = isNumber(value) ? value : value(c);
      return getTargetValue(target, { max, min, });
    });
  };
  const inc = (delta: number = 1) => {
    setValue((c) => c + delta);
  };
  const dec = (delta: number = 1) => {
    setValue((c) => c - delta);
  };
  const set = (value: ValueParam) => {
    setValue(value);
  };
  const reset = () => {
    setValue(initialValue);
  };
  return [
    current,
    {
      inc: useMemoizedFn(inc),
      dec: useMemoizedFn(dec),
      set: useMemoizedFn(set),
      reset: useMemoizedFn(reset),
    },
  ] as const;

useDebounceEffect

  const [flag, setFlag] = useState({});
  const { run } = useDebounceFn(() => {
    setFlag({});
  }, options);
  useEffect(() => {
    return run();
  }, deps);
  useUpdateEffect(effect, [flag]);

useDocumentVisibility

  const [documentVisibility, setDocumentVisibility] = useState(getVisibility);
  useEventListener(
    'visibilitychange',
    () => { setDocumentVisibility(getVisibility()); },
    { target: () => document, },
  );
  return documentVisibility;
const getVisibility = () => {
  if (!isBrowser)return 'visible';
  return document.visibilityState;
};

useDrag & useDrop TODO

useDynamicList TODO

useEventEmitter🌺

  const ref = useRef<EventEmitter<T>>();
  if (!ref.current) {
    ref.current = new EventEmitter();
  }
  return ref.current;
----------------------------
class EventEmitter<T> {
  private subscriptions = new Set<Subscription<T>>();
  emit = (val: T) => {
    for (const subscription of this.subscriptions) {
      subscription(val);
    }
  };
  useSubscription = (callback: Subscription<T>) => {
    const callbackRef = useRef<Subscription<T>>();
    callbackRef.current = callback;
    useEffect(() => {
      function subscription(val: T) {
        if (callbackRef.current) {
          callbackRef.current(val);
        }
      }
      this.subscriptions.add(subscription);
      return () => {
        this.subscriptions.delete(subscription);
      };
    }, []);
  };
}

useEventListener

  const { enable = true } = options;
  const handlerRef = useLatest(handler);
  useEffectWithTarget(
    () => {
      if (!enable) return;
      const targetElement = getTargetElement(options.target, window);
      if (!targetElement?.addEventListener) return;
      const eventListener = (event: Event) => {
        return handlerRef.current(event);
      };
      targetElement.addEventListener(eventName, eventListener, {
        capture: options.capture, once: options.once, passive: options.passive,
      });
      return () => {
        targetElement.removeEventListener(eventName, eventListener, {
          capture: options.capture,
        });
      };
    },
    [eventName, options.capture, options.once, options.passive, enable],
    options.target,
  );

useEventTarget

  const { initialValue, transformer } = options || {};
  const [value, setValue] = useState(initialValue);
  const transformerRef = useLatest(transformer);
  const reset = useCallback(() => setValue(initialValue), []);
  const onChange = useCallback((e: EventTarget<U>) => {
    const _value = e.target.value;
    if (isFunction(transformerRef.current)) {
      return setValue(transformerRef.current(_value));
    }
    return setValue(_value as unknown as T);
  }, []);
  return [
    value,
    { onChange, reset },
  ] as const;

useExternal

useFavicon

  useEffect(() => {
    if (!href) return;
    const cutUrl = href.split('.');
    const imgSuffix = cutUrl[cutUrl.length - 1].toLocaleUpperCase() as ImgTypes;
    const link: HTMLLinkElement =
      document.querySelector("link[rel*='icon']") || document.createElement('link');
    link.type = ImgTypeMap[imgSuffix];
    link.href = href;
    link.rel = 'shortcut icon';
    document.getElementsByTagName('head')[0].appendChild(link);
  }, [href]);

useFocusWithin

  const [isFocusWithin, setIsFocusWithin] = useState(false);
  const { onFocus, onBlur, onChange } = options || {};
  useEventListener(
    'focusin',
    (e: FocusEvent) => {
      if (!isFocusWithin) {
        onFocus?.(e);
        onChange?.(true);
        setIsFocusWithin(true);
      }
    },
    {
      target,
    },
  );
  useEventListener(
    'focusout',
    (e: FocusEvent) => {
      if (isFocusWithin && !(e.currentTarget as Element)?.contains?.(e.relatedTarget as Element)) {
        onBlur?.(e);
        onChange?.(false);
        setIsFocusWithin(false);
      }
    },
    {
      target,
    },
  );
  return isFocusWithin;

useFullscreen

useFusionTable TODO

useGetState

  const [state, setState] = useState(initialState);
  const stateRef = useLatest(state);
  const getState = useCallback(() => stateRef.current, []);
  return [state, setState, getState];

useHistoryTravel

useHover

  const { onEnter, onLeave, onChange } = options || {};
  const [state, { setTrue, setFalse }] = useBoolean(false);
  useEventListener(
    'mouseenter',
    () => {
      onEnter?.();
      setTrue();
      onChange?.(true);
    },
    { target, },
  );
  useEventListener(
    'mouseleave',
    () => {
      onLeave?.();
      setFalse();
      onChange?.(false);
    },
    { target, },
  );
  return state;

useInfiniteScroll🌺

...
if (scrollHeight <= scrollTop + clientHeight + threshold) loadMore();
...

useInterval

  const timerCallback = useMemoizedFn(fn);
  const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
  const clear = useCallback(() => {
    if (timerRef.current) clearInterval(timerRef.current);
  }, []);
  useEffect(() => {
    if (!isNumber(delay) || delay < 0) return;
    if (options.immediate) timerCallback();
    timerRef.current = setInterval(timerCallback, delay);
    return clear;
  }, [delay, options.immediate]);
  return clear;

useInViewport🌺

const observer = new IntersectionObserver()
observer.observe(el)
observer.disconnect()
isIntersecting: 一个布尔值,表示目标元素当前是否与根元素交叉。
intersectionRatio:目标元素的可见比例,即intersectionRect占boundingClientRect的比例,完全可见时为1,完全不可见时小于1

useLocalStorageState

useLongPress TODO

useMap

useMouse

useNetwork

window.addEventListener('online', onOnline);
window.addEventListener('offline', onOnline);
navigator.connection.addEventListener('change', onConnectionChange);
------------------------------------------------------------------
function getConnectionProperty(): NetworkState {
  const c = getConnection();
  if (!c) return {};
  return {
    rtt: c.rtt,
    type: c.type,
    saveData: c.saveData,
    downlink: c.downlink,
    downlinkMax: c.downlinkMax,
    effectiveType: c.effectiveType,
  };
}

usePagination TODO

useRafInterval

  const handle: Handle = {
    id: 0,
  };
  const loop = () => {
    const current = Date.now();
    if (current - start >= delay) {
      callback();
      start = Date.now();
    }
    handle.id = requestAnimationFrame(loop);
  };
  handle.id = requestAnimationFrame(loop);

useRafState

  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;
...
cancelAnimationFrame(handle.id);

useRafTimeout

  const handle: Handle = {
    id: 0,
  };
  const startTime = new Date().getTime();
  const loop = () => {
    const current = new Date().getTime();
    if (current - startTime >= delay) {
      callback();
    } else {
      handle.id = requestAnimationFrame(loop);
    }
  };
  handle.id = requestAnimationFrame(loop);

useResetState

  const [state, setState] = useState(initialState);
  const resetState = useMemoizedFn(() => {
    setState(initialState);
  });
  return [state, setState, resetState];

useSelections TODO

useSessionStorageState

useSet

  const getInitValue = () => new Set(initialValue);
  const [set, setSet] = useState<Set<K>>(getInitValue);
  const add = (key: K) => {
    if (set.has(key)) return;
    setSet((prevSet) => {
      const temp = new Set(prevSet);
      temp.add(key);
      return temp;
    });
  };
  const remove = (key: K) => {
    if (!set.has(key)) return;
    setSet((prevSet) => {
      const temp = new Set(prevSet);
      temp.delete(key);
      return temp;
    });
  };
  const reset = () => setSet(getInitValue());
  return [
    set,
    {
      add: useMemoizedFn(add),
      remove: useMemoizedFn(remove),
      reset: useMemoizedFn(reset),
    },
  ] as const;

useTextSelection🌺🌺

el.addEventListener('mouseup', mouseupHandler);
document.addEventListener('mousedown', mousedownHandler);
...
const text = window.getSelection()?.toString();
...
window.getSelection().removeAllRanges();
---------------------------------------------------------------
function getRectFromSelection(selection: Selection | null): Rect {
  if (!selection) return initRect;
  if (selection.rangeCount < 1) return initRect;
  const range = selection.getRangeAt(0);
  const { height, width, top, left, right, bottom } = range.getBoundingClientRect();
  return { height, width, top, left, right, bottom, };
}

useThrottle🌺🌺🌺

  const [throttled, setThrottled] = useState(value);
  const { run } = useThrottleFn(() => {
    setThrottled(value);
  }, options);
  useEffect(() => {
    run();
  }, [value]);
  return throttled;

useThrottleEffect🌺🌺🌺

  const [flag, setFlag] = useState({});
  const { run } = useThrottleFn(() => {
    setFlag({});
  }, options);
  useEffect(() => {
    return run();
  }, deps);
  useUpdateEffect(effect, [flag]);

useThrottleFn

  const fnRef = useLatest(fn);
  const wait = options?.wait ?? 1000;
  const throttled = useMemo(
    () =>
      throttle(
        (...args: Parameters<T>): ReturnType<T> => {
          return fnRef.current(...args);
        },
        wait,
        options,
      ),
    [],
  );
  useUnmount(() => {
    throttled.cancel();
  });
  return {
    run: throttled,
    cancel: throttled.cancel,
    flush: throttled.flush,
  };

useTitle

useToggle

  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, };
  }, []);

  return [state, actions];

useTrackedEffect

  const previousDepsRef = useRef<T>();
  useEffect(() => {
    const changes = diffTwoDeps(previousDepsRef.current, deps);
    const previousDeps = previousDepsRef.current;
    previousDepsRef.current = deps;
    return effect(changes, previousDeps, deps);
  }, deps);

useVirtualList TODO

useWebSocket🌺🌺🌺

useWhyDidYouUpdate

  const prevProps = useRef<IProps>({});
  useEffect(() => {
    if (prevProps.current) {
      const allKeys = Object.keys({ ...prevProps.current, ...props });
      const changedProps: IProps = {};
      allKeys.forEach((key) => {
        if (!Object.is(prevProps.current[key], props[key])) {
          changedProps[key] = {
            from: prevProps.current[key],
            to: props[key],
          };
        }
      });
      if (Object.keys(changedProps).length) console.log('[why-did-you-update]', componentName, changedProps);
    }
    prevProps.current = props;
  });

useMount

useEffect(() => {
  fn?.();
}, []);

useUnmount

const fnRef = useLatest(fn);
useEffect(
  () => () => {
    fnRef.current();
  },
  [],
);

useSetState

const [state, setState] = useState<S>(initialState);
const setMergeState = useCallback((patch) => {
  setState((prevState) => {
    const newState = isFunction(patch) ? patch(prevState) : patch;
    return newState ? { ...prevState, ...newState } : prevState;
  });
}, []);
return [state, setMergeState];

useDebounce ??🌟🌟

  const [debounced, setDebounced] = useState(value);
  const { run } = useDebounceFn(() => {
    setDebounced(value);
  }, options);
  useEffect(() => {
    run();
  }, [value]);
  return debounced;

usePrevious:usePrevious(state, shouldUpdate)

const prevRef = useRef<T>();
const curRef = useRef<T>();
if (shouldUpdate(curRef.current, state)) {
  prevRef.current = curRef.current;
  curRef.current = state;
}
return prevRef.current;

useUnmountedRef

  const unmountedRef = useRef(false);
  useEffect(() => {
    unmountedRef.current = false;
    return () => {
      unmountedRef.current = true;
    };
  }, []);
  return unmountedRef;

useSafeState

  const unmountedRef = useUnmountedRef();
  const [state, setState] = useState(initialState);
  const setCurrentState = useCallback((currentState) => {
    if (unmountedRef.current) return;/** if component is unmounted, stop update */
    setState(currentState);
  }, []);
  return [state, setCurrentState] as const;

useGetState

  const [state, setState] = useState(initialState);
  const stateRef = useLatest(state);
  const getState = useCallback(() => stateRef.current, []);
  return [state, setState, getState];

useUpdateEffect

    const isMounted = useRef(false);
    hook(() => {
      return () => {
        isMounted.current = false;
      };
    }, []);
    hook(() => {
      if (!isMounted.current) {
        isMounted.current = true;
      } else {
        return effect();
      }
    }, deps);

useAsyncEffect

    const e = effect();
    let cancelled = false;
    async function execute() {
      if (isAsyncGenerator(e)) {
        while (true) {
          const result = await e.next();
          if (result.done || cancelled) {
            break;
          }
        }
      } else {
        await e;
      }
    }
    execute();
    return () => {
      cancelled = true;
    };

useInterval

  const timerCallback = useMemoizedFn(fn);
  const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
  const clear = useCallback(() => {
    if (timerRef.current) {
      clearInterval(timerRef.current);
    }
  }, []);
  useEffect(() => {
    if (!isNumber(delay) || delay < 0) {
      return;
    }
    if (options.immediate) {
      timerCallback();
    }
    timerRef.current = setInterval(timerCallback, delay);
    return clear;
  }, [delay, options.immediate]);
  return clear;

useTimeout

  const timerCallback = useMemoizedFn(fn);
  const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const clear = useCallback(() => {
    if (timerRef.current) {
      clearTimeout(timerRef.current);
    }
  }, []);
  useEffect(() => {
    if (!isNumber(delay) || delay < 0) {
      return;
    }
    timerRef.current = setTimeout(timerCallback, delay);
    return clear;
  }, [delay]);

  return clear;

useLockFn

  const lockRef = useRef(false);
  return useCallback(
    async (...args: P) => {
      if (lockRef.current) return;
      lockRef.current = true;
      try {
        const ret = await fn(...args);
        return ret;
      } catch (e) {
        throw e;
      } finally {
        lockRef.current = false;
      }
    },
    [fn],
  );

useUpdate 🌺

  const [, setState] = useState({});
  return useCallback(() => setState({}), []);

useScroll 🌺🌺

  const [position, setPosition] = useRafState<Position>();
  const shouldUpdateRef = useLatest(shouldUpdate);
  useEffectWithTarget(
    () => {
      const el = getTargetElement(target, document);
      if (!el) return;
      const updatePosition = () => {
        let newPosition: Position;
        if (el === document) {
          if (document.scrollingElement) {
            newPosition = { left: document.scrollingElement.scrollLeft, top: document.scrollingElement.scrollTop };
          } else {
            newPosition = {
              left: Math.max(
                window.pageXOffset,
                document.documentElement.scrollLeft,
                document.body.scrollLeft,
              ),
              top: Math.max(
                window.pageYOffset,
                document.documentElement.scrollTop,
                document.body.scrollTop,
              ),
            };
          }
        } else {
          newPosition = {
            left: (el as Element).scrollLeft,
            top: (el as Element).scrollTop,
          };
        }
        if (shouldUpdateRef.current(newPosition)) setPosition(newPosition);
      };
      updatePosition();
      el.addEventListener('scroll', updatePosition);
      return () => {
        el.removeEventListener('scroll', updatePosition);
      };
    },
    [],
    target,
  );
  return position;

useRafState

  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;

useSize

  const [state, setState] = useRafState<Size | undefined>(
    () => {
      const el = getTargetElement(target);
      return el ? { width: el.clientWidth, height: el.clientHeight } : undefined
    },
  );

  useIsomorphicLayoutEffectWithTarget(
    () => {
      const el = getTargetElement(target);
      if (!el) return;
      const resizeObserver = new ResizeObserver((entries) => {
        entries.forEach((entry) => {
          const { clientWidth, clientHeight } = entry.target;
          setState({ width: clientWidth, height: clientHeight });
        });
      });
      resizeObserver.observe(el);
      return () => {
        resizeObserver.disconnect();
      };
    },
    [],
    target,
  );

  return state;

useControllableValue

  const {defaultValue,defaultValuePropName = 'defaultValue',valuePropName = 'value',trigger = 'onChange'} = options;
  const value = props[valuePropName] as T;
  const isControlled = props.hasOwnProperty(valuePropName);
  const initialValue = useMemo(() => {
    if (isControlled) return value;
    if (props.hasOwnProperty(defaultValuePropName)) return props[defaultValuePropName];
    return defaultValue;
  }, []);
  const stateRef = useRef(initialValue);
  if (isControlled) stateRef.current = value;
  const update = useUpdate();
  function setState(v: SetStateAction<T>, ...args: any[]) {
    const r = isFunction(v) ? v(stateRef.current) : v;
    if (!isControlled) {
      stateRef.current = r;
      update();
    }
    if (props[trigger]) {
      props[trigger](r, ...args);
    }
  }
  return [stateRef.current, useMemoizedFn(setState)] as const;

useCreation

  const { current } = useRef({
    deps,
    obj: undefined as undefined | T,
    initialized: false,
  });
  if (current.initialized === false || !depsAreSame(current.deps, deps)) {
    current.deps = deps;
    current.obj = factory();
    current.initialized = true;
  }
  return current.obj as T;

useLatest

const ref = useRef(value);
ref.current = value;

useMemoizedFn🌺🌺🌺

  const fnRef = useRef<T>(fn);
  fnRef.current = useMemo<T>(() => fn, [fn]);🌺🌺
  const memoizedFn = useRef<PickFunction<T>>();
  if (!memoizedFn.current) {
    memoizedFn.current = function (this, ...args) {
      return fnRef.current.apply(this, args);
    };
  }
  return memoizedFn.current as T;

useReactive🌺🌺

  const update = useUpdate();
  const stateRef = useRef<S>(initialState);
  const state = useCreation(() => {
    return observer(stateRef.current, () => {
      update();
    });
  }, []);
  return state;
----------------------------

function observer<T extends Record<string, any>>(initialVal: T, cb: () => void): T {
  const existingProxy = proxyMap.get(initialVal);
  if (existingProxy) return existingProxy;
  if (rawMap.has(initialVal)) return initialVal;
  const proxy = new Proxy<T>(initialVal, {
    get(target, key, receiver) {
      const res = Reflect.get(target, key, receiver);
      const descriptor = Reflect.getOwnPropertyDescriptor(target, key);
      if (!descriptor?.configurable && !descriptor?.writable)  return res;
      return isPlainObject(res) || Array.isArray(res) ? observer(res, cb) : res;
    },
    set(target, key, val) {
      const ret = Reflect.set(target, key, val);
      cb();
      return ret;
    },
    deleteProperty(target, key) {
      const ret = Reflect.deleteProperty(target, key);
      cb();
      return ret;
    },
  });
  proxyMap.set(initialVal, proxy);
  rawMap.set(proxy, initialVal);
  return proxy;
}

Others

ResizeObserver

1. new ResizeObserver() 2.ins.observe(el) 3.ins.disconnect()
const resizeObserver = new ResizeObserver((entries) => {
    entries.forEach((entry) => {
      const { clientWidth, clientHeight } = entry.target;
      ...
    });
});
resizeObserver.observe(el);
resizeObserver.disconnect();
⚠️ **GitHub.com Fallback** ⚠️