4 changed files with 255 additions and 29 deletions
@ -0,0 +1,120 @@ |
|||
import { throttle } from '../index' |
|||
|
|||
describe('throttle', () => { |
|||
let callback: ReturnType<typeof vi.fn> |
|||
|
|||
beforeEach(() => { |
|||
// 在每个测试用例前重置 mock 函数
|
|||
callback = vi.fn() |
|||
// 重置定时器模拟
|
|||
vi.useFakeTimers() |
|||
}) |
|||
|
|||
afterEach(() => { |
|||
// 清理定时器模拟
|
|||
vi.restoreAllMocks() |
|||
}) |
|||
|
|||
it('应该在指定时间内只执行一次', () => { |
|||
const throttled = throttle(callback, 1000) |
|||
|
|||
// 连续调用多次
|
|||
throttled() |
|||
throttled() |
|||
throttled() |
|||
|
|||
expect(callback).toHaveBeenCalledTimes(1) |
|||
|
|||
// 前进 500ms
|
|||
vi.advanceTimersByTime(500) |
|||
throttled() |
|||
expect(callback).toHaveBeenCalledTimes(1) |
|||
|
|||
// 前进到 1000ms
|
|||
vi.advanceTimersByTime(500) |
|||
throttled() |
|||
expect(callback).toHaveBeenCalledTimes(2) |
|||
}) |
|||
|
|||
it('应该正确传递参数和this上下文', () => { |
|||
const context = { value: 'test' } |
|||
const throttled = throttle(function(this: any, arg: string) { |
|||
expect(this).toBe(context) |
|||
expect(arg).toBe('arg') |
|||
callback() |
|||
}, 1000) |
|||
|
|||
throttled.call(context, 'arg') |
|||
expect(callback).toHaveBeenCalledTimes(1) |
|||
}) |
|||
|
|||
it('应该在leading=false时忽略第一次调用', () => { |
|||
const throttled = throttle(callback, 1000, { leading: false }) |
|||
|
|||
throttled() |
|||
expect(callback).not.toHaveBeenCalled() |
|||
|
|||
vi.advanceTimersByTime(1000) |
|||
throttled() |
|||
expect(callback).toHaveBeenCalledTimes(1) |
|||
}) |
|||
|
|||
it('应该在trailing=false时忽略最后一次调用', () => { |
|||
const throttled = throttle(callback, 1000, { trailing: false }) |
|||
|
|||
throttled() |
|||
throttled() |
|||
vi.advanceTimersByTime(1000) |
|||
|
|||
expect(callback).toHaveBeenCalledTimes(1) |
|||
}) |
|||
|
|||
it('应该在取消后停止执行', () => { |
|||
const throttled = throttle(callback, 1000) |
|||
|
|||
throttled() |
|||
throttled() |
|||
throttled.cancel() |
|||
|
|||
vi.advanceTimersByTime(1000) |
|||
expect(callback).toHaveBeenCalledTimes(1) |
|||
}) |
|||
|
|||
it('应该处理边界情况下的时间间隔', () => { |
|||
const throttled = throttle(callback, 0) |
|||
|
|||
throttled() |
|||
throttled() |
|||
|
|||
expect(callback).toHaveBeenCalledTimes(2) |
|||
}) |
|||
|
|||
it('应该正确处理异步函数', async () => { |
|||
const asyncFn = vi.fn().mockResolvedValue('result') |
|||
const throttled = throttle(asyncFn, 1000) |
|||
|
|||
const result1 = await throttled() |
|||
const result2 = await throttled() |
|||
|
|||
expect(asyncFn).toHaveBeenCalledTimes(1) |
|||
expect(result1).toBe('result') |
|||
expect(result2).toBe('result') |
|||
}) |
|||
|
|||
it('应该在remaining <= 0时正确执行', () => { |
|||
const throttled = throttle(callback, 1000) |
|||
|
|||
throttled() |
|||
throttled() |
|||
expect(callback).toHaveBeenCalledTimes(1) |
|||
vi.advanceTimersByTime(1000) |
|||
|
|||
throttled() |
|||
throttled() |
|||
expect(callback).toHaveBeenCalledTimes(2) |
|||
vi.advanceTimersByTime(200) |
|||
expect(callback).toHaveBeenCalledTimes(2) |
|||
vi.advanceTimersByTime(800) |
|||
expect(callback).toHaveBeenCalledTimes(3) |
|||
}) |
|||
}) |
@ -1,17 +1,54 @@ |
|||
export function throttle<T extends any[], R = void>(fn: (...argu: T) => R, interval: number = 200) { |
|||
let last; |
|||
let timer: ReturnType<typeof setTimeout> | void; |
|||
return function (this: void, ...argu: T) { |
|||
const now = +new Date(); |
|||
if (last && now - last < interval) { |
|||
timer && clearTimeout(timer); |
|||
export function throttle<T extends any[], R = void>( |
|||
fn: (...args: T) => R, |
|||
wait: number = 200, |
|||
options: { leading?: boolean; trailing?: boolean } = {} |
|||
) { |
|||
const { leading = true, trailing = true } = options; |
|||
let lastTime = 0; |
|||
let timer: ReturnType<typeof setTimeout> | null = null; |
|||
let lastResult: R; |
|||
|
|||
function invoke(this: any, args: T): R { |
|||
lastResult = fn.apply(this, args); |
|||
lastTime = Date.now(); |
|||
return lastResult; |
|||
} |
|||
|
|||
const throttled = function(this: any, ...args: T): R | undefined { |
|||
const now = Date.now(); |
|||
const remaining = wait - (now - lastTime); |
|||
|
|||
if (!lastTime && !leading) { |
|||
lastTime = now; |
|||
return; |
|||
} |
|||
|
|||
if (remaining <= 0) { |
|||
if (timer) { |
|||
clearTimeout(timer); |
|||
timer = null; |
|||
} |
|||
return invoke.call(this, args); |
|||
} |
|||
|
|||
if (trailing && !timer) { |
|||
timer = setTimeout(() => { |
|||
last = now; |
|||
fn.apply(this, argu); |
|||
}, interval); |
|||
} else { |
|||
last = now; |
|||
fn.apply(this, argu); |
|||
timer = null; |
|||
invoke.call(this, args); |
|||
}, remaining); |
|||
} |
|||
|
|||
return lastResult; |
|||
}; |
|||
|
|||
// 添加 cancel 方法
|
|||
throttled.cancel = function() { |
|||
if (timer) { |
|||
clearTimeout(timer); |
|||
timer = null; |
|||
} |
|||
lastTime = 0; |
|||
}; |
|||
|
|||
return throttled; |
|||
} |
|||
|
@ -1,27 +1,96 @@ |
|||
|
|||
|
|||
## 开发 |
|||
# xyx-utils 🚀 |
|||
|
|||
**开发新模块** |
|||
[](https://github.com/npmrun/xyx-utils/blob/main/LICENSE) |
|||
[](https://github.com/npmrun/xyx-utils/releases) |
|||
|
|||
1. 在`packages\.vitepress\config.ts`增加`startsDirs`和`alias`。 |
|||
2. 在`internal\tsconfig\tsconfig.json`增加路径别名 |
|||
3. 在`vitest.shared.ts`修改别名 |
|||
模块化的工具库集合,提供跨平台、多场景的实用功能。 |
|||
|
|||
**本地调试工具** |
|||
## 功能特性 |
|||
|
|||
- yalc |
|||
- **多模块支持**:包含 Core、Node、Vue3 等独立模块 |
|||
- **现代化构建**:基于 Vite & TypeScript 构建 |
|||
- **完善测试**:Vitest 单元测试覆盖 |
|||
- **模块化架构**:使用 PNPM Workspace 管理多包 |
|||
|
|||
据说是本地最好的link方案,有没有吹牛自己试试。 |
|||
## 安装 |
|||
|
|||
**版本管理** |
|||
```bash |
|||
# 安装核心模块 |
|||
pnpm add @xyx-utils/core |
|||
|
|||
- changeset |
|||
- changeset publish |
|||
- pnpm publish -r --access=public # --report-summary |
|||
# 安装Node工具 |
|||
pnpm add @xyx-utils/node |
|||
|
|||
可以尝试尝试 |
|||
# 安装Vue3工具 |
|||
pnpm add @xyx-utils/vue3 |
|||
``` |
|||
|
|||
**测试** |
|||
## 模块列表 |
|||
|
|||
1. 运行指定模块的测试文件: `pnpm test core src/common` |
|||
| 模块名称 | 描述 | 文档地址 | |
|||
|------------|-----------------------|--------------------------| |
|||
| @xyx-utils/core | 基础工具函数集合 | [文档](./packages/core) | |
|||
| @xyx-utils/node | Node.js 运行时工具 | [文档](./packages/node) | |
|||
| @xyx-utils/vue3 | Vue3 组件工具库 | [文档](./packages/vue3) | |
|||
| @xyx-utils/shared| 共享类型定义 | [文档](./packages/shared) | |
|||
|
|||
## 快速开始 |
|||
|
|||
```typescript |
|||
// 使用核心模块 |
|||
import { formatDate } from '@xyx-utils/core'; |
|||
|
|||
const currentDate = new Date(); |
|||
const formatted = formatDate(currentDate, 'YYYY-MM-DD'); |
|||
const obj = { a: 1 }; |
|||
``` |
|||
|
|||
## 开发指南 |
|||
|
|||
### 开发新模块 |
|||
|
|||
1. **配置路径别名** |
|||
- 在 `packages/.vitepress/config.ts` 添加 `startsDirs` 和 `alias` |
|||
- 更新 `internal/tsconfig/tsconfig.json` 路径别名 |
|||
- 修改 `vitest.shared.ts` 测试配置 |
|||
|
|||
2. **本地调试** |
|||
```bash |
|||
# 使用 yalc 进行本地依赖链接 |
|||
yalc add @xyx/core |
|||
``` |
|||
|
|||
### 版本管理 |
|||
|
|||
使用 Changeset 进行版本控制: |
|||
```bash |
|||
pnpm changeset:add # 创建变更集 |
|||
pnpm changeset:version # 版本升级 |
|||
pnpm release # 完整发布流程 |
|||
``` |
|||
|
|||
## 测试 |
|||
|
|||
运行指定模块测试: |
|||
```bash |
|||
pnpm test core |
|||
``` |
|||
|
|||
查看测试覆盖率: |
|||
```bash |
|||
pnpm run coverage |
|||
``` |
|||
|
|||
## 贡献 |
|||
|
|||
欢迎通过 Issue 和 PR 参与贡献!请先阅读[贡献指南](./CONTRIBUTING.md)。 |
|||
|
|||
## License |
|||
|
|||
MIT © [npmrun](https://github.com/npmrun) |
|||
|
|||
--- |
|||
|
|||
📖 完整文档请访问 [项目文档](https://npmrun.github.io/xyx-utils/) |
|||
|
Loading…
Reference in new issue