Preview Updated 2026-05-10

Composables & Hooks 工具函数

ChuFix 提供的 25 个零 UI、零依赖工具函数。Vue 端走 composition API、React 端走 hooks,命名与参数完全对齐。

English translation pending This page hasn't been translated yet — falling back to Chinese. PRs welcome on GitHub.

ChuFix 把”组件之外但开发里反复要写”的逻辑收成两套工具函数:@chufix-design/vue 走 Vue 3 组合式 API、@chufix-design/react 走 React 18 hooks。两套实现的函数名、参数顺序、返回字段一一对齐,方便跨框架团队对照。

// Vue 3
import { useDebouncedFn, useClickOutside, useLocalStorage } from '@chufix-design/vue';

// React 18
import { useDebouncedFn, useClickOutside, useLocalStorage } from '@chufix-design/react';

⚠ 这些 utility 必须走 barrel 导入(@chufix-design/vue / @chufix-design/react),不要走 …/src/... 深路径——深路径会绕过 dist 单 chunk,导致 module 单例失效。


异步与提交保护

useSingleFlight —— 合并并发调用

同一时刻只允许一份执行,并发调用复用同一个 Promise;适合”避免重复请求”。

const flight = useSingleFlight();
const data = await flight.run(() => fetchUser(id));

useSubmitGuard —— 防重复提交

isSubmitting 状态 + cooldownMs 冷却期;冷却期内调用会被跳过并写入 lastSkipped

const guard = useSubmitGuard({ cooldownMs: 1500 });
async function onSubmit() {
  await guard.run(async () => {
    await api.createOrder();
  });
}
// guard.isSubmitting / guard.lastError / guard.lastSkipped / guard.reset()

useAsync —— 异步状态管家

data / error / loading 三态 + run / reset;并发调用会丢弃过期结果(通过 callSeq)。

const { data, error, loading, run } = useAsync(fetchUser, { immediate: true });

useRetry —— 指数退避重试

baseMs * 2^attempt + jitter,支持 maxAttemptsshouldRetry(err)

await useRetry(() => fetch(url), { maxAttempts: 3, baseMs: 200 });

usePolling —— 周期性拉取

pauseOnHidden(默认 true)让浏览器隐藏时停止;immediate 决定挂载是否立即触发;调用重叠时跳过本轮。

const poll = usePolling(fetchStatus, { intervalMs: 5000, immediate: true });
poll.start(); poll.stop();

节流与防抖

useDebouncedRef / useDebouncedValue

  • Vue: useDebouncedRef(initial, delayMs) 返回 Ref<T>,写入后延迟生效。
  • React: useDebouncedValue(value, delayMs) 返回延迟后的值。
// Vue
const search = useDebouncedRef('', 300);

// React
const debounced = useDebouncedValue(search, 300);

useDebouncedFn —— 防抖函数

返回带 cancel / flush 的可调用对象。

const onResize = useDebouncedFn(() => recompute(), 200);
onResize();      // 触发
onResize.flush(); // 立即执行 pending 调用
onResize.cancel();

useThrottledFn —— 节流函数

支持 leading / trailing 配置。

const onScroll = useThrottledFn(() => track(), 100, { leading: true, trailing: true });

手势

useDrag —— 通用拖拽底座

基于 pointerdown + setPointerCapture 的拖拽追踪:累计位移、最近 100ms 速度、轴向限制、bounds 夹紧、阈值起拖。BottomSheet / PullToRefresh / SwipeActionDrawer 下滑关闭都建立在它之上,组件外也可直接消费。

const ref = useTemplateRef('panel');
useDrag(ref, {
  axis: 'y',
  bounds: { min: 0 },
  onMove(s) { offset.value = s.dy; },
  onEnd(s) { if (s.vy > 0.3) close(); },
});

useSwipe —— 四方向识别

useDrag 之上的轻量包装:在释放时根据”位移阈值或速度阈值”判定 'up' / 'down' / 'left' / 'right',已被 Carousel 接入用于左右滑切换。

useSwipe(viewportRef, {
  axis: 'x',
  threshold: 32,
  velocity: 0.3,
  onSwipe(dir) {
    if (dir === 'left') next();
    if (dir === 'right') prev();
  },
});

拖拽 / Drag & Drop

CfDraggable / CfDroppable / CfDragLayer 共享一个 module-singleton store;下面这三个 composable / hook 暴露同一个底座,便于自己装组件。必须 barrel import —— deep src/ import 会创建第二份 store 实例导致 drop 静默失败。

useDraggable —— 把元素装成拖拽源

const el = ref<HTMLElement | null>(null);
useDraggable(el, {
  payload: () => ({ type: 'metric', data: { id: 'cpu' } }),
  handle: '.grip',
  onStart: () => console.log('drag start'),
  onEnd: (dropped) => console.log('dropped?', dropped),
});

useDroppable —— 注册放置目标

const target = ref<HTMLElement | null>(null);
const { isOver, canDrop } = useDroppable(target, {
  accept: ['metric', 'task'],
  onDrop(payload, pointer) { /* ... */ },
});

useDragDrop —— 订阅全局拖拽状态

返回响应式 / hook-state 快照:active / payload / source / pointer / origin / over / canDrop,用来画自己的拖拽预览 / 状态条 / 多重 drop 提示。

const dnd = useDragDrop();
watchEffect(() => {
  if (dnd.active) console.log('pointer at', dnd.pointer);
});

表单

useFormValidation —— Schema 表单状态

接受 initialValues 与可选 schema(每字段一个或多个 validator)。Validator 同步或异步均可,返回错误字符串或 undefined。返回值含 values / errors / touched / isValid / isDirty / isSubmitting、以及 setValue / setError / setTouched / validate / reset / submitvalidateOn 支持 'change' | 'blur' | 'submit'

const form = useFormValidation({
  initialValues: { name: '', email: '' },
  validateOn: 'change',
  schema: {
    name: (v) => (!v ? '必填' : undefined),
    email: [
      (v) => (!v ? '必填' : undefined),
      async (v) => ((await checkEmail(v)) ? undefined : '已被占用'),
    ],
  },
});

await form.submit(async (values) => {
  await api.create(values);
});

Vue / React 两端 API 镜像:Vue 返回值即响应式 Proxy,React 用 useState 重渲。CfFormSchema 把该 hook 与字段定义对接,常常一起使用。

DOM 与事件

useEventListener —— 自动挂载/卸载

接受 window / document / ref / RefObject 等多种 target,组件卸载时自动 detach。

useEventListener(window, 'keydown', (e) => { /* ... */ });
useEventListener(buttonRef, 'click', onClick);

useClickOutside —— 点外关闭

监听 pointerdown;适合 dropdown / popover / 抽屉。

// Vue
const popRef = ref<HTMLElement | null>(null);
useClickOutside(popRef, () => (open.value = false));

// React
const popRef = useRef<HTMLDivElement>(null);
useClickOutside(popRef, () => setOpen(false));

useFocusTrap —— Tab/Shift+Tab 困住焦点

Modal / Drawer / CommandPalette 必用。卸载或 enabled=false 时自动归还焦点。

useFocusTrap(modalRef, () => open.value);   // Vue:传 getter 或 ref
useFocusTrap(modalRef, open);                // React:传 boolean

useScrollLock —— 锁滚动

引用计数:多个组件同时锁也只锁一次;自动补 paddingRight 避免页面跳动。

useHotkeys —— 快捷键

mod 自动适配 Mac (Meta) / Win/Linux (Ctrl);默认忽略 input/textarea 内的击键。

useHotkeys({
  'mod+k': () => openPalette(),
  'esc': () => close(),
});

useMediaQuery —— 媒体查询

const isMobile = useMediaQuery('(max-width: 640px)');

useIntersectionObserver —— 可见性

once: true 命中后自动停止(懒加载、曝光统计)。

useResizeObserver —— 尺寸观察

回调收到 ResizeObserverEntry,无需自己 new。


存储

useLocalStorage / useSessionStorage

跨标签页同步(监听 storage 事件);序列化默认 JSON.parse/stringify,可传 serializer 自定义。

// Vue: 返回 Ref<T>
const theme = useLocalStorage('cf-theme', 'dark');

// React: 返回 [value, setValue]
const [theme, setTheme] = useLocalStorage('cf-theme', 'dark');

useClipboard —— 复制到剪贴板

优先 navigator.clipboard,失败回退到 document.execCommand('copy')

const { copy, copied, copy: write } = useClipboard();
await copy('hello');

主题与偏好

useTheme —— 读写 [data-theme]

监听 <html>data-theme 变化,支持 'dark-cool' / 'dark-warm' / 'light'

const theme = useTheme();
theme.set('light');     // 写入 <html data-theme>
theme.cycle();          // 在三套之间循环

useDensity —— 读写 [data-density]

同上,对应 'comfortable' / 'compact'

useReducedMotion —— 用户的减弱动画偏好

封装 prefers-reduced-motion: reduce


小工具

函数说明
useId(prefix?)生成稳定 ID;React 端基于 React.useId,Vue 端基于自增计数
usePrevious(value)返回上一帧的值
useToggle(initial?){ state, toggle, set, on, off } 布尔翻转
useTimeout(fn, ms, { startOnMount? })start / cancel 的 setTimeout
useInterval(fn, ms, { startOnMount? })start / cancel 的 setInterval
useCounter(initial?, { min?, max? }){ count, inc, dec, set, reset } 受限计数

命名差异

只有 1 处微差:useDebouncedRef(Vue,自定义 ref) vs useDebouncedValue(React,返回值)。Vue 也导出了 useDebouncedValue 别名以方便跨端共享代码。其余 24 个函数名两端完全一致。

SSR 与 window

所有依赖 window / document 的 utility 都在组件挂载后才访问 DOM;SSR 期间它们返回安全的默认值(如 useMediaQuery 在 SSR 返回 false)。Vue 端在 onMounted 中初始化,React 端在 useEffect 中初始化。

反馈与讨论

Composables & Hooks 工具函数 · Discussion

0
0 / 600
正在加载评论...