# 相关链接
- react (opens new window) react官网
# hooks
# 节点尺寸变化时执行回调
点击查看代码
import { useEffect, type RefObject } from "react";
export type Target = HTMLElement | RefObject<HTMLElement>;
export type Callback = (size: { width: number; height: number }) => void;
/**
* DOM 节点尺寸变化时执行回调
* @param target DOM 节点或者 ref
* @param callback 执行的回调函数
*/
export default function useDomSize(target: Target, callback: Callback) {
useEffect(() => {
const element = "current" in target ? target.current : target;
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const { clientWidth, clientHeight } = entry.target;
callback({ width: clientWidth, height: clientHeight });
}
});
if (element) resizeObserver.observe(element);
return () => {
resizeObserver.disconnect();
};
}, [target, callback]);
}
# 判断容器内容是否溢出
点击查看代码
import { type RefObject } from "react";
import { useBoolean } from "ahooks";
import useDomSize from "./useDomSize";
/** 获取dom padding */
const getPadding = (el: Element) => {
const style = window.getComputedStyle(el, null);
const paddingLeft = Number.parseInt(style.paddingLeft, 10) || 0;
const paddingRight = Number.parseInt(style.paddingRight, 10) || 0;
const paddingTop = Number.parseInt(style.paddingTop, 10) || 0;
const paddingBottom = Number.parseInt(style.paddingBottom, 10) || 0;
return {
left: paddingLeft,
right: paddingRight,
top: paddingTop,
bottom: paddingBottom,
};
};
export type Target = HTMLElement | RefObject<HTMLElement>;
/**
* 判断容器内容是否溢出
* @param target DOM 节点或者 ref
*/
export default function useContentOverflow(target: Target) {
const [isOverflowX, { set: setIsOverflowX }] = useBoolean(false);
const [isOverflowY, { set: setIsOverflowY }] = useBoolean(false);
useDomSize(target, () => {
const element = "current" in target ? target.current : target;
if (element) {
const range = document.createRange();
range.setStart(element, 0);
range.setEnd(element, element.childNodes.length);
const padding = getPadding(element);
const rangeSize = range.getBoundingClientRect();
setIsOverflowX(
rangeSize.width + padding.left + padding.right > element.offsetWidth ||
element.scrollWidth > element.offsetWidth
);
setIsOverflowY(
rangeSize.height + padding.top + padding.bottom >
element.offsetHeight || element.scrollHeight > element.offsetHeight
);
}
});
return { isOverflowX, isOverflowY };
}
# 防止loading闪烁
点击查看代码
import { useState, useRef } from "react";
import { useBoolean } from "ahooks";
// 使用场景:有些时候,当请求返回足够快的情况下,loading 可能会在短时间内完成 false -> true -> false 状态的切换,
// 这时候,加载动画可能会出现闪烁的情况(特别是占满一整屏的动画),这给会用户带来糟糕的体验。
const getTime = () => new Date().getTime(); // 获取当前时间戳
type resultType = [
loading: boolean,
actions: { setTrue(): void; setFalse(): void },
];
/**
* 延迟 `loading` 变成 `true`,如果在 `delay` 时间内调用 `setFalse`,则 `loading` 不会变成 `true`,有效防止闪烁。
*
* 处理逻辑:如果请求返回过快,则直接不显示 `loading`。
*/
export const useLoadingDelay = function (
val: boolean,
delay: number = 500
): resultType {
const [loading, { set }] = useBoolean(val);
const timerRef = useRef<number | null>(null);
const setTrue = () => {
if (timerRef.current) return; // 感觉没必要重复设置
timerRef.current = window.setTimeout(() => set(true), delay || 0);
};
const setFalse = () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
set(false);
};
return [loading, { setTrue, setFalse }];
};
/**
* 让 `loading` 持续 `delay` 时间以上。
*
* 处理逻辑:如果请求时间少于 `delay`,则持续时间为 `delay`,如果请求时间大于 `delay`,则最终时间为实际请求的时间。
*/
export const useLoadingKeep = function (
val: boolean,
delay: number = 500
): resultType {
const [loading, { set }] = useBoolean(val);
const timerRef = useRef<number | null>(null);
const [timer, setTimer] = useState<number | null>(null);
const setTrue = () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
set(true);
setTimer(getTime());
};
const setFalse = () => {
if (timerRef.current) return; // 感觉没必要重复设置
const currentTime = getTime();
const formerTime = timer || currentTime;
const runTime = currentTime - formerTime; // loading已经运行的时间
timerRef.current = window.setTimeout(
() => set(false),
runTime > delay ? 0 : delay - runTime
);
setTimer(null); // 清除timer,避免重复setFalse
};
return [loading, { setTrue, setFalse }];
};
/**
*
* 不管是单独使用 `useLoadingDelay` 还是单独使用 `useLoadingKeep`,效果都不是最好。
*
* 所以它们两个可以中和一下,最终的表现是:
* 如果在 `delay` 时间内完成了请求,那就不展示 `loading` 动画,超过才进行展示;
* 如果展示了 `loading` 动画,那至少要展示 `keep` 时间,不能一闪而过。
*/
export default function useLoadingDelayAndKeep(
val: boolean = false,
options?: { delay: number; keep: number }
): resultType {
const [loading, { set }] = useBoolean(val);
const timerRef = useRef<number | null>(null);
const [timer, setTimer] = useState<number | null>(null);
const _options = { delay: 300, keep: 500, ...options };
const setTrue = () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
timerRef.current = window.setTimeout(() => {
set(true);
setTimer(getTime());
}, _options.delay || 0);
};
const setFalse = () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
const { keep = 0 } = _options;
const currentTime = getTime();
const formerTime = timer || currentTime;
const runTime = currentTime - formerTime; // loading已经运行的时间
timerRef.current = window.setTimeout(
() => set(false),
runTime > keep ? 0 : keep - runTime
);
setTimer(null); // 清除timer,避免重复setFalse
};
return [loading, { setTrue, setFalse }];
}
← vue