Skip to content

默认入口 ​

ts
import { /* ... */ } from '@tripo3d/utils';

默认入口聚合了浏览器侧的通用工具:设备检测、Promise 工具、跳转/下载、文件名/MIME、文件读取、localStorage 包装、事件总线。

设备检测 ​

isMobileDevice ​

函数签名说明
isMobileDevice(ua: string | null) => boolean识别 UA 是否为移动端浏览器(android / webos / iphone / ipod / blackberry / iemobile / opera mini)
ts
import { isMobileDevice } from '@tripo3d/utils';

if (isMobileDevice(navigator.userAgent)) {
  // 走移动端路径
}

为什么要传参

显式传入 UA 而不是内部读 navigator,便于 SSR 场景下从请求 header 注入。空值返回 false,不会抛错。

Promise 工具 ​

sleep ​

函数签名说明
sleep(ms: number) => Promise<void>延迟 N 毫秒解决 Promise,常用于节流、动画衔接
ts
import { sleep } from '@tripo3d/utils';

await sleep(300);
console.log('300ms 后执行');

pf(promise factory) ​

外部解构 resolve / reject 的 Promise 工厂——适合把"等用户操作完成"封成 Promise(如等待弹窗确认、等待 OAuth 回调)。

函数签名说明
pf<T, U = any>() => { promise: Promise<T>, resolve: (v) => void, reject: (e) => void }返回 promise 与外部可调用的 resolve/reject
ts
import { pf } from '@tripo3d/utils';

function waitForLogin() {
  const { promise, resolve } = pf<string>();
  // 在某个事件回调里调 resolve('token')
  document.addEventListener('login-done', e => resolve(e.detail), { once: true });
  return promise;
}

const token = await waitForLogin();

跳转 / 下载 ​

jump 和 download 都是基于动态 <a> 元素 + .click() 实现,无需手动管理 DOM。

函数签名说明
jump(url: string, blank = true) => void导航到 URL,默认在新标签页打开。blank=false 替换当前页
download(url: string, name?: string) => void触发浏览器下载。name 设置文件名(需同源或服务端配置 Content-Disposition)
ts
import { download, jump } from '@tripo3d/utils';

jump('https://tripo3d.ai');                      // 新标签页打开
jump('/dashboard', false);                       // 当前页跳转

download('https://example.com/file.zip');        // 下载,文件名由响应头决定
download('https://example.com/file.zip', 'my.zip'); // 下载并重命名(需同源)

跨源下载文件名

浏览器只在同源或服务端 Content-Disposition 配合时才会接受 download 属性的文件名。跨源 CDN 上的文件,用户最终拿到的可能是原始文件名。

文件名 / MIME ​

getSuffix ​

函数签名说明
getSuffix(file: File | string) => string | undefined提取文件扩展名(小写)。传 File 对象时,扩展名缺失会回退到 MIME 后段
ts
import { getSuffix } from '@tripo3d/utils';

getSuffix('https://cdn.example.com/x/a.PNG');    // 'png'
getSuffix(new File([], 'photo.JPEG'));           // 'jpeg'
getSuffix(new File([], 'no-extension', { type: 'image/webp' })); // 'webp'(回退到 MIME)

readFileAsDataURL ​

函数签名说明
readFileAsDataURL(file: File) => Promise<string>FileReader 包装,把 File 读成 base64 data URL。失败时 reject
ts
import { readFileAsDataURL } from '@tripo3d/utils';

const dataUrl = await readFileAsDataURL(file); // 'data:image/png;base64,...'
img.src = dataUrl;

localStorage 包装 ​

带 JSON 序列化的 localStorage。仅在浏览器环境可用,SSR 时直接调用会抛 localStorage is not defined。

函数签名说明
getStorage<T>(key: string) => T | nullJSON.parse 失败或 key 不存在返回 null。不会抛错
setStorage(key: string, value: unknown) => voidJSON.stringify 后写入。值过大会触发 QuotaExceededError(未捕获)
removeStorage(key: string) => void删除 key
ts
import { getStorage, removeStorage, setStorage } from '@tripo3d/utils';

interface UserPref {
  theme: 'light' | 'dark';
  lang: string;
}

setStorage('user-pref', { theme: 'dark', lang: 'zh-CN' });
const pref = getStorage<UserPref>('user-pref');
if (pref) console.log(pref.theme);

removeStorage('user-pref');

事件总线 ​

通用 pub/sub,无 DOM / 框架依赖,可被 Node、Worker、浏览器统一消费。

函数签名说明
createEventBus<TEventMap>() => EventBus<TEventMap>创建事件总线。泛型可选,传入事件映射后 on / once / off / emit 都受类型约束;不传退化为 string + unknown

EventBus API ​

方法签名说明
on(type, handler) => () => void订阅事件,返回解绑函数
once(type, handler) => () => void订阅一次性事件,首次触发后自动解绑;返回值可在触发前提前取消
off(type, handler) => void手动解绑;通道无订阅者时自动清理键
emit(type, payload) => void广播载荷。emit 期间复制 handler 集合迭代,handler 内部调用 off / clear 不影响本次派发
has(type, handler?) => boolean省略 handler 时检查通道是否有订阅者;传入 handler 时检查该 handler 是否注册
clear() => void清空所有订阅
ts
import { createEventBus } from '@tripo3d/utils';

// 无泛型:通道为 string,载荷为 unknown。
const bus = createEventBus();
const off = bus.on('login', (user) => console.log(user));
bus.emit('login', { id: 1 });
off();

// 带泛型:on / emit / off 全部受类型约束。
interface AppEvents {
  'cart:add': { sku: string; qty: number };
  'cart:clear': void;
}
const app = createEventBus<AppEvents>();
app.on('cart:add', payload => console.log(payload.sku));  // payload 被推断为 { sku, qty }
app.emit('cart:add', { sku: 'A1', qty: 2 });

// once + has。
app.once('cart:clear', () => console.log('cleared once'));
app.has('cart:clear'); // true
app.emit('cart:clear', undefined);
app.has('cart:clear'); // false (once 已自动解绑)

once 的解绑语义

once(type, handler) 内部包装了一层 wrapper,所以 off(type, originalHandler) 无法匹配 once 注册的项。用 once 返回的解绑函数取消才是合法路径。has(type, originalHandler) 同理只匹配 on 注册的 handler。

下一步 ​

  • Date — dayjs 包装与时区
  • Resource — AWS S3 上传
  • Playground — 工具函数交互测试 + Tripo 图标集浏览

基于 MIT 协议发布(内部使用)