ahooks - Sophiekx/Sophiekx.github.io GitHub Wiki
feature:基于Ts、支持 SSR、无闭包问题、丰富的基础&高级 Hooks。求场生态当作高调
// 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。
🌰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 (加载内外插件)=> 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);
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;
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;
const [flag, setFlag] = useState({});
const { run } = useDebounceFn(() => {
setFlag({});
}, options);
useEffect(() => {
return run();
}, deps);
useUpdateEffect(effect, [flag]);
const [documentVisibility, setDocumentVisibility] = useState(getVisibility);
useEventListener(
'visibilitychange',
() => { setDocumentVisibility(getVisibility()); },
{ target: () => document, },
);
return documentVisibility;
const getVisibility = () => {
if (!isBrowser)return 'visible';
return document.visibilityState;
};
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);
};
}, []);
};
}
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,
);
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;
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]);
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;
const [state, setState] = useState(initialState);
const stateRef = useLatest(state);
const getState = useCallback(() => stateRef.current, []);
return [state, setState, getState];
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;
...
if (scrollHeight <= scrollTop + clientHeight + threshold) loadMore();
...
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;
const observer = new IntersectionObserver()
observer.observe(el)
observer.disconnect()
isIntersecting: 一个布尔值,表示目标元素当前是否与根元素交叉。
intersectionRatio:目标元素的可见比例,即intersectionRect占boundingClientRect的比例,完全可见时为1,完全不可见时小于1
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,
};
}
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);
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);
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);
const [state, setState] = useState(initialState);
const resetState = useMemoizedFn(() => {
setState(initialState);
});
return [state, setState, resetState];
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;
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, };
}
const [throttled, setThrottled] = useState(value);
const { run } = useThrottleFn(() => {
setThrottled(value);
}, options);
useEffect(() => {
run();
}, [value]);
return throttled;
const [flag, setFlag] = useState({});
const { run } = useThrottleFn(() => {
setFlag({});
}, options);
useEffect(() => {
return run();
}, deps);
useUpdateEffect(effect, [flag]);
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,
};
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];
const previousDepsRef = useRef<T>();
useEffect(() => {
const changes = diffTwoDeps(previousDepsRef.current, deps);
const previousDeps = previousDepsRef.current;
previousDepsRef.current = deps;
return effect(changes, previousDeps, deps);
}, deps);
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;
});
useEffect(() => {
fn?.();
}, []);
const fnRef = useLatest(fn);
useEffect(
() => () => {
fnRef.current();
},
[],
);
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];
const [debounced, setDebounced] = useState(value);
const { run } = useDebounceFn(() => {
setDebounced(value);
}, options);
useEffect(() => {
run();
}, [value]);
return debounced;
const prevRef = useRef<T>();
const curRef = useRef<T>();
if (shouldUpdate(curRef.current, state)) {
prevRef.current = curRef.current;
curRef.current = state;
}
return prevRef.current;
const unmountedRef = useRef(false);
useEffect(() => {
unmountedRef.current = false;
return () => {
unmountedRef.current = true;
};
}, []);
return unmountedRef;
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;
const [state, setState] = useState(initialState);
const stateRef = useLatest(state);
const getState = useCallback(() => stateRef.current, []);
return [state, setState, getState];
const isMounted = useRef(false);
hook(() => {
return () => {
isMounted.current = false;
};
}, []);
hook(() => {
if (!isMounted.current) {
isMounted.current = true;
} else {
return effect();
}
}, deps);
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;
};
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;
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;
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],
);
const [, setState] = useState({});
return useCallback(() => setState({}), []);
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;
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;
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;
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;
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;
const ref = useRef(value);
ref.current = value;
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;
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;
}
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();