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) { |
export function throttle<T extends any[], R = void>( |
||||
let last; |
fn: (...args: T) => R, |
||||
let timer: ReturnType<typeof setTimeout> | void; |
wait: number = 200, |
||||
return function (this: void, ...argu: T) { |
options: { leading?: boolean; trailing?: boolean } = {} |
||||
const now = +new Date(); |
) { |
||||
if (last && now - last < interval) { |
const { leading = true, trailing = true } = options; |
||||
timer && clearTimeout(timer); |
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(() => { |
timer = setTimeout(() => { |
||||
last = now; |
timer = null; |
||||
fn.apply(this, argu); |
invoke.call(this, args); |
||||
}, interval); |
}, remaining); |
||||
} else { |
} |
||||
last = now; |
|
||||
fn.apply(this, argu); |
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 |
# 安装Node工具 |
||||
- changeset publish |
pnpm add @xyx-utils/node |
||||
- pnpm publish -r --access=public # --report-summary |
|
||||
|
|
||||
可以尝试尝试 |
# 安装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