更新记录
1.1.1(2025-11-26)
下载此版本
1.1.0(2025-11-10)
下载此版本
- [新增] useNetwork 网络状态监听
- [新增] useDebounceFn 防抖函数
- [新增] useThrottleFn 节流函数
1.0.0(2025-11-09)
下载此版本
查看更多
平台兼容性
uni-app(4.16)
| Vue2 |
Vue3 |
Chrome |
Safari |
app-vue |
app-nvue |
Android |
iOS |
鸿蒙 |
| √ |
√ |
√ |
√ |
√ |
√ |
√ |
√ |
√ |
| 微信小程序 |
支付宝小程序 |
抖音小程序 |
百度小程序 |
快手小程序 |
京东小程序 |
鸿蒙元服务 |
QQ小程序 |
飞书小程序 |
快应用-华为 |
快应用-联盟 |
| √ |
√ |
√ |
√ |
√ |
√ |
√ |
√ |
√ |
√ |
√ |
oui-composables
基于 Vue 3 的组合式函数库(Composables),为 uni-app 提供常用的业务逻辑封装与工具函数。
安装
# 通过 uni_modules 安装
# 将 oui-composables 文件夹放入项目的 uni_modules 目录中
API 概览
- useCounter(initialValue?, options?) => [current, actions]
- useNetwork(options?) => { start, stop }
- useRequest<TData, TParams>(service, options?) => { data, error, loading, run, runAsync, refresh, refreshAsync, cancel, mutate }
- useDebounceFn(fn, wait, options?) => debouncedFn
- useThrottleFn(fn, wait, options?) => throttledFn
useCounter
签名
function useCounter(
initialValue?: number,
options?: { min?: number; max?: number },
): readonly [
/** 当前值,只读 Ref */
Readonly<import('vue').Ref<number>>,
{
/** 增加(默认 +1) */
inc: (delta?: number) => void;
/** 减少(默认 -1) */
dec: (delta?: number) => void;
/** 设置新值或更新函数 */
set: (value: number | ((current: number) => number)) => void;
/** 重置为初始值 */
reset: () => void;
},
];
示例
import { useCounter } from '@/uni_modules/oui-composables';
const [count, { inc, dec, set, reset }] = useCounter(0, { min: 0, max: 10 });
inc(); // +1
dec(2); // -2
set((c) => c + 5); // 基于当前值更新
reset(); // 回到 0
useNetwork
签名
type NetworkType = '2g' | '3g' | '4g' | '5g' | 'wifi' | 'ethernet' | 'unknown' | 'none';
interface NetworkState {
type: NetworkType;
}
function useNetwork(options?: {
onReady?: (data: NetworkState) => void;
onChange?: (data: NetworkState) => void;
}): {
/** 开始监听网络状态变化 */
start: () => void;
/** 停止监听网络状态变化 */
stop: () => void;
};
示例
import { onMounted, onBeforeUnmount } from 'vue';
import { useNetwork } from '@/uni_modules/oui-composables';
const { start, stop } = useNetwork({
onReady({ type }) {
console.log('初始网络类型', type); // 'wifi' | '4g' | 'none' | 'unknown' ...
},
onChange({ type }) {
console.log('网络变化', type);
},
});
onMounted(() => start());
onBeforeUnmount(() => stop());
useRequest
签名
type Service<TData, TParams extends any[]> = (...args: TParams) => Promise<TData>;
function useRequest<TData, TParams extends any[] = any[]>(
service: Service<TData, TParams>,
options?: {
// 触发控制
manual?: boolean | import('vue').WatchSource<boolean>;
ready?: boolean | import('vue').WatchSource<boolean>;
defaultParams?: TParams;
// 生命周期
onBefore?: (params: TParams) => void;
onSuccess?: (data: TData, params: TParams) => void;
onError?: (error: Error, params: TParams) => void;
onFinally?: (params: TParams, data?: TData, error?: Error) => void;
// 防抖
debounceWait?: number | import('vue').WatchSource<number>;
debounceOptions?:
| {
leading?: boolean;
trailing?: boolean;
maxWait?: number;
}
| import('vue').WatchSource<{
leading?: boolean;
trailing?: boolean;
maxWait?: number;
}>;
// 节流
throttleWait?: number | import('vue').WatchSource<number>;
throttleOptions?:
| {
leading?: boolean;
trailing?: boolean;
}
| import('vue').WatchSource<{
leading?: boolean;
trailing?: boolean;
}>;
// 轮询
pollingInterval?: number | import('vue').WatchSource<number>;
pollingErrorRetryCount?: number | import('vue').WatchSource<number>;
pollingWhenHidden?: boolean | import('vue').WatchSource<boolean>;
// 重试
retryCount?: number | import('vue').WatchSource<number>;
retryInterval?: number | import('vue').WatchSource<number>;
// 聚焦刷新与依赖刷新
refreshOnWindowFocus?: boolean | import('vue').WatchSource<boolean>;
focusTimespan?: number | import('vue').WatchSource<number>;
refreshDeps?: import('vue').WatchSource<any>[];
refreshDepsAction?: () => void;
// 延迟 loading
loadingDelay?: number | import('vue').WatchSource<number>;
},
): {
data: Readonly<import('vue').Ref<TData | undefined>>;
error: Readonly<import('vue').Ref<Error | undefined>>;
loading: Readonly<import('vue').Ref<boolean>>;
run: (...params: TParams) => void;
runAsync: (...params: TParams) => Promise<TData>;
refresh: () => void;
refreshAsync: () => Promise<TData>;
cancel: () => void;
mutate: (data?: TData | ((oldData?: TData) => TData)) => void;
};
示例 1:基础请求
import { useRequest } from '@/uni_modules/oui-composables';
const getUser = (id: number) =>
new Promise<{ id: number; name: string }>((resolve, reject) => {
uni.request({
url: `https://example.com/api/users/${id}`,
method: 'GET',
success: (res) => resolve(res.data as any),
fail: (err) => reject(err),
});
});
const { data, error, loading, run, refresh, cancel, mutate } = useRequest(getUser, {
defaultParams: [1], // 挂载后自动以 [1] 请求一次
onSuccess(d) {
console.log('success', d);
},
onError(e) {
console.error('error', e);
},
loadingDelay: 150, // 短请求避免 loading 闪烁
});
// 手动请求
run(2);
// 使用上次参数刷新
refresh();
// 乐观更新
mutate((old) => ({ ...(old || {}), name: 'updated' }));
// 取消当前进行中的请求或轮询
cancel();
示例 2:防抖与节流
import { useRequest } from '@/uni_modules/oui-composables';
const search = (q: string) =>
new Promise<string[]>((resolve) => {
uni.request({
url: `https://example.com/api/search?q=${encodeURIComponent(q)}`,
success: (res) => resolve(res.data as any),
});
});
const { data, run } = useRequest(search, {
// 防抖:输入结束 300ms 后请求
debounceWait: 300,
// 若改用节流:
// throttleWait: 300, throttleOptions: { leading: true, trailing: true },
});
run('vue3'); // 短时间多次触发将被防抖/节流策略控制
示例 3:轮询与聚焦刷新
import { useRequest } from '@/uni_modules/oui-composables';
const getStatus = () =>
new Promise<{ online: boolean }>((resolve) => {
uni.request({
url: 'https://example.com/api/status',
success: (res) => resolve(res.data as any),
});
});
const { data, refresh } = useRequest(getStatus, {
pollingInterval: 5000, // 每 5 秒自动刷新
pollingErrorRetryCount: 3, // 错误时最多连续重试 3 次
pollingWhenHidden: false, // 应用不可见时暂停轮询
refreshOnWindowFocus: true, // 回到前台时触发刷新
focusTimespan: 5000, // 聚焦刷新节流窗口
});
示例 4:错误重试
import { useRequest } from '@/uni_modules/oui-composables';
const flaky = () =>
new Promise<string>((resolve, reject) => {
uni.request({
url: 'https://example.com/api/flaky',
success: (res) =>
Math.random() < 0.5 ? resolve(res.data as any) : reject(new Error('random fail')),
fail: (err) => reject(err),
});
});
const { data, error, runAsync } = useRequest(flaky, {
retryCount: 2, // 共尝试 3 次(初次 + 2 次重试)
retryInterval: 1000, // 每次重试间隔 1s
});
// run 会吞掉异常并打印控制台;runAsync 会抛出异常
runAsync().catch((e) => console.error('caught by caller', e));
示例 5:依赖刷新(refreshDeps)
import { ref } from 'vue';
import { useRequest } from '@/uni_modules/oui-composables';
const page = ref(1);
const list = (p: number) =>
new Promise<{ items: any[] }>((resolve) => {
uni.request({
url: `https://example.com/api/list?page=${p}`,
success: (res) => resolve(res.data as any),
});
});
const { data } = useRequest(list, {
defaultParams: [page.value],
refreshDeps: [page], // page 变化时自动复用上次参数刷新
});
// 翻页时触发刷新
page.value = 2;
示例 6:mutate 乐观更新
import { useRequest } from '@/uni_modules/oui-composables';
const getProfile = () =>
new Promise<{ name: string; likes: number }>((resolve) => {
uni.request({
url: 'https://example.com/api/profile',
success: (res) => resolve(res.data as any),
});
});
const { data, mutate } = useRequest(getProfile, { defaultParams: [] });
// 立即更新本地数据,无需等待服务端
mutate((old) => ({ ...(old || { name: '', likes: 0 }), likes: (old?.likes || 0) + 1 }));
useDebounceFn
签名
function useDebounceFn<T extends (...args: any[]) => any>(
fn: T,
wait: number,
options?: import('lodash-es').DebounceOptions,
): {
run: T;
cancel: () => void;
};
示例
import { useDebounceFn } from '@/uni_modules/oui-composables';
const debounced = useDebounceFn((q: string) => {
console.log('debounced', q);
}, 300);
debounced.run('vue3'); // 300ms 后打印 'debounced vue3'
useThrottleFn
签名
function useThrottleFn<T extends (...args: any[]) => any>(
fn: T,
wait: number,
options?: import('lodash-es').ThrottleOptions,
): {
run: T;
cancel: () => void;
};
示例
import { useThrottleFn } from '@/uni_modules/oui-composables';
const throttled = useThrottleFn((q: string) => {
console.log('throttled', q);
}, 300);
throttled.run('vue3'); // 立即打印 'throttled vue3',后续每 300ms 打印一次