更新记录

1.1.1(2025-11-26) 下载此版本

  • [修复] useNetwork 修复网络状态监听

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 打印一次

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议