@alwatr/debounce 1.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/LICENSE +373 -0
- package/README.md +468 -0
- package/dist/debounce.d.ts +133 -0
- package/dist/debounce.d.ts.map +1 -0
- package/dist/main.cjs +145 -0
- package/dist/main.cjs.map +7 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.mjs +118 -0
- package/dist/main.mjs.map +7 -0
- package/dist/type.d.ts +41 -0
- package/dist/type.d.ts.map +1 -0
- package/package.json +81 -0
package/README.md
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
# Alwatr Debounce
|
|
2
|
+
|
|
3
|
+
A powerful, modern, and type-safe debouncer utility designed for high-performance applications. It's framework-agnostic, works seamlessly in both Node.js and browsers, and provides a rich API for fine-grained control over function execution.
|
|
4
|
+
|
|
5
|
+
[](https://www.google.com/search?q=https://www.npmjs.com/package/%40alwatr/debounce)
|
|
6
|
+
[](https://www.google.com/search?q=https://www.npmjs.com/package/%40alwatr/nanolib)
|
|
7
|
+
[](https://www.google.com/search?q=alwatr+debounce)
|
|
8
|
+
[](https://www.google.com/search?q=alwatr+nanolib)
|
|
9
|
+
[](https://www.google.com/search?q=alwatr)
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **💪 Type-Safe:** Fully written in TypeScript for excellent autocompletion and type safety.
|
|
14
|
+
- **⚙️ Highly Configurable:** Control execution with `leading` and `trailing` edge options.
|
|
15
|
+
- **🎯 Precise `this` Context:** Explicitly define `thisContext` to prevent common JavaScript pitfalls.
|
|
16
|
+
- **🔄 Lifecycle-Aware:** Includes `cancel()` and `flush()` methods for complete control, crucial for preventing memory leaks in component-based frameworks.
|
|
17
|
+
- **🌐 Universal:** Works in any JavaScript environment, including browsers, Node.js, Deno, and Bun.
|
|
18
|
+
- **🌳 Tree-Shakable:** Designed with modern ESM standards for optimal bundle sizes.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Using npm
|
|
24
|
+
npm i @alwatr/debounce
|
|
25
|
+
|
|
26
|
+
# Using yarn
|
|
27
|
+
yarn add @alwatr/debounce
|
|
28
|
+
|
|
29
|
+
# Using pnpm
|
|
30
|
+
pnpm add @alwatr/debounce
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Core Concepts
|
|
36
|
+
|
|
37
|
+
### What is Debouncing?
|
|
38
|
+
|
|
39
|
+
Debouncing is a technique to limit the rate at which a function gets called. It ensures that your code only executes _once_ after a specific period of inactivity.
|
|
40
|
+
|
|
41
|
+
**Analogy:** Imagine a user typing in a search bar. You don't want to send an API request for every single keystroke (`'s'`, `'se'`, `'sea'`, ...). Instead, you wait until the user **stops typing** for a moment (e.g., 300ms) and then send a single API request for the final text (`'search'`). This prevents unnecessary network requests and improves performance.
|
|
42
|
+
|
|
43
|
+
### `leading` vs. `trailing` Edge
|
|
44
|
+
|
|
45
|
+
The timing of the debounced function's execution is controlled by the `leading` and `trailing` options.
|
|
46
|
+
|
|
47
|
+
- **`trailing: true` (Default Behavior)**
|
|
48
|
+
The function is executed **after** the delay period, following the _last_ trigger. This is the classic debounce behavior, ideal for search inputs or calculations.
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
trigger--trigger--trigger----(delay)----> EXECUTE
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- **`leading: true`**
|
|
55
|
+
The function is executed **immediately** on the _first_ trigger. Subsequent triggers within the delay window are ignored. This is useful for actions where immediate feedback is desired, like clicking a button that should not be spammed.
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
EXECUTE <--trigger--trigger--trigger----(delay)----> (no execution)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- **`leading: true` AND `trailing: true`**
|
|
62
|
+
The function executes on the **first** trigger and also on the **last** trigger that occurs after the delay window. This is useful for scenarios requiring both immediate and final actions.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Quick Start
|
|
67
|
+
|
|
68
|
+
The easiest way to get started is with the `createDebouncer` factory function.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import {createDebouncer} from '@alwatr/debounce';
|
|
72
|
+
|
|
73
|
+
// 1. Create a debouncer instance
|
|
74
|
+
const debouncer = createDebouncer({
|
|
75
|
+
// The function you want to debounce
|
|
76
|
+
callback: (query: string) => {
|
|
77
|
+
console.log(`Searching for: ${query}`);
|
|
78
|
+
},
|
|
79
|
+
// The delay in milliseconds
|
|
80
|
+
delay: 300,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// 2. Trigger it multiple times
|
|
84
|
+
console.log('User is typing...');
|
|
85
|
+
debouncer.trigger('Alw');
|
|
86
|
+
debouncer.trigger('Alwat');
|
|
87
|
+
debouncer.trigger('Alwatr');
|
|
88
|
+
|
|
89
|
+
// After 300ms of inactivity, the console will log:
|
|
90
|
+
// "Searching for: Alwatr"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## API Reference
|
|
94
|
+
|
|
95
|
+
### `createDebouncer(config)`
|
|
96
|
+
|
|
97
|
+
A factory function that creates a new `Debouncer` instance. It's the recommended way to create debouncers for better type inference.
|
|
98
|
+
|
|
99
|
+
### `DebouncerConfig`
|
|
100
|
+
|
|
101
|
+
This is the configuration object passed to `createDebouncer` or the `Debouncer` constructor.
|
|
102
|
+
|
|
103
|
+
| Property | Type | Description | Default |
|
|
104
|
+
| :------------ | :---------------------- | :----------------------------------------------------------------------- | :---------- |
|
|
105
|
+
| `callback` | `F extends AnyFunction` | **(Required)** The function to be debounced. | - |
|
|
106
|
+
| `delay` | `number` | **(Required)** The debounce delay in milliseconds. | - |
|
|
107
|
+
| `thisContext` | `ThisParameterType<F>` | The `this` context for the callback. Essential when using class methods. | `undefined` |
|
|
108
|
+
| `leading` | `boolean` | If `true`, executes the function on the leading edge. | `false` |
|
|
109
|
+
| `trailing` | `boolean` | If `true`, executes the function on the trailing edge. | `true` |
|
|
110
|
+
|
|
111
|
+
### `Debouncer` Instance
|
|
112
|
+
|
|
113
|
+
An instance of the `Debouncer` class returned by `createDebouncer`.
|
|
114
|
+
|
|
115
|
+
#### Properties
|
|
116
|
+
|
|
117
|
+
- **`isPending`**: `boolean` (Getter)
|
|
118
|
+
Returns `true` if a debounced function is scheduled for execution.
|
|
119
|
+
|
|
120
|
+
#### Methods
|
|
121
|
+
|
|
122
|
+
- **`trigger(...args: Parameters<F>): void`**
|
|
123
|
+
Triggers the debounce timer. Each call resets the timer. The arguments passed here will be forwarded to the `callback` function.
|
|
124
|
+
|
|
125
|
+
- **`cancel(): void`**
|
|
126
|
+
Cancels any pending execution and clears internal state. This is crucial for preventing memory leaks.
|
|
127
|
+
|
|
128
|
+
- **`flush(): void`**
|
|
129
|
+
Immediately executes the pending function if one exists, bypassing the delay. If no call is pending, it does nothing.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Advanced Usage & Best Practices
|
|
134
|
+
|
|
135
|
+
### **⚠️ Important: Lifecycle Management & Memory Leaks**
|
|
136
|
+
|
|
137
|
+
In modern Single-Page Applications (SPAs) or any component-based architecture, components are frequently created and destroyed. If a `Debouncer` instance is active when its associated component is destroyed, its internal `setTimeout` can keep a reference to the component, preventing it from being garbage collected. **This is a memory leak.**
|
|
138
|
+
|
|
139
|
+
**Solution:** Always call `debouncer.cancel()` in the cleanup phase of your component or class.
|
|
140
|
+
|
|
141
|
+
#### Example with a Plain Class
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
class MyComponent {
|
|
145
|
+
private debouncer = createDebouncer({
|
|
146
|
+
callback: this.doSomething,
|
|
147
|
+
thisContext: this, // Bind `this` correctly!
|
|
148
|
+
delay: 500,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
constructor() {
|
|
152
|
+
window.addEventListener('resize', this.debouncer.trigger);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
doSomething() {
|
|
156
|
+
console.log('Window resized!', this);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// The crucial cleanup method
|
|
160
|
+
destroy() {
|
|
161
|
+
window.removeEventListener('resize', this.debouncer.trigger);
|
|
162
|
+
this.debouncer.cancel(); // Prevents memory leaks!
|
|
163
|
+
console.log('Component destroyed and debouncer cleaned up.');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### Conceptual Example in React
|
|
169
|
+
|
|
170
|
+
```jsx
|
|
171
|
+
import {useEffect, useMemo} from 'react';
|
|
172
|
+
import {createDebouncer} from '@alwatr/debounce';
|
|
173
|
+
|
|
174
|
+
function MyComponent() {
|
|
175
|
+
const debouncedApiCall = useMemo(
|
|
176
|
+
() =>
|
|
177
|
+
createDebouncer({
|
|
178
|
+
callback: (query) => fetch(`/api/search?q=${query}`),
|
|
179
|
+
delay: 300,
|
|
180
|
+
}),
|
|
181
|
+
[],
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
// This is the cleanup function.
|
|
186
|
+
// It runs when the component is unmounted.
|
|
187
|
+
return () => {
|
|
188
|
+
debouncedApiCall.cancel(); // VERY IMPORTANT!
|
|
189
|
+
};
|
|
190
|
+
}, [debouncedApiCall]);
|
|
191
|
+
|
|
192
|
+
return <input onChange={(e) => debouncedApiCall.trigger(e.target.value)} />;
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Using with `thisContext`
|
|
197
|
+
|
|
198
|
+
When your callback is a method on a class, `this` can lose its context. Pass the class instance to `thisContext` to ensure it's bound correctly.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
class ApiService {
|
|
202
|
+
private debouncer = createDebouncer({
|
|
203
|
+
callback: this.sendRequest,
|
|
204
|
+
thisContext: this, // Ensures `this` inside `sendRequest` is `ApiService`
|
|
205
|
+
delay: 500,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
constructor(private endpoint: string) {}
|
|
209
|
+
|
|
210
|
+
public queueRequest(data: unknown) {
|
|
211
|
+
this.debouncer.trigger(data);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private sendRequest(data: unknown) {
|
|
215
|
+
console.log(`Sending data to ${this.endpoint}:`, data);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const service = new ApiService('/users');
|
|
220
|
+
service.queueRequest({name: 'user1'});
|
|
221
|
+
service.queueRequest({name: 'user2'}); // Will only send the last request
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Contributing
|
|
225
|
+
|
|
226
|
+
Contributions are welcome\! Please feel free to open an issue or submit a pull request.
|
|
227
|
+
|
|
228
|
+
## License
|
|
229
|
+
|
|
230
|
+
This project is licensed under the **MPL-2.0**.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
<br />
|
|
235
|
+
<div dir="rtl">
|
|
236
|
+
|
|
237
|
+
# Alwatr Debounce (راهنمای فارسی)
|
|
238
|
+
|
|
239
|
+
یک ابزار دیبانس (debounce) قدرتمند، مدرن و تایپ-سیف (Type-Safe) که برای اپلیکیشنهای با کارایی بالا طراحی شده است. این پکیج به هیچ فریمورکی وابسته نیست، به راحتی در محیطهای Node.js و مرورگر کار میکند و یک API غنی برای کنترل دقیق بر روی اجرای توابع فراهم میکند.
|
|
240
|
+
|
|
241
|
+
[](https://www.google.com/search?q=https://www.npmjs.com/package/%40alwatr/debounce)
|
|
242
|
+
[](https://www.google.com/search?q=https://www.npmjs.com/package/%40alwatr/nanolib)
|
|
243
|
+
[](https://www.google.com/search?q=alwatr+debounce)
|
|
244
|
+
[](https://www.google.com/search?q=alwatr+nanolib)
|
|
245
|
+
[](https://www.google.com/search?q=alwatr)
|
|
246
|
+
|
|
247
|
+
## ویژگیها
|
|
248
|
+
|
|
249
|
+
- **💪 تایپ-سیف (Type-Safe):** به طور کامل با TypeScript نوشته شده تا از راهنمای خودکار کد (autocompletion) و ایمنی نوعدادهها بهرهمند شوید.
|
|
250
|
+
- **⚙️ قابلیت تنظیم بالا:** اجرای توابع را با گزینههای `leading` (اجرا در ابتدا) و `trailing` (اجرا در انتها) کنترل کنید.
|
|
251
|
+
- **🎯 مدیریت دقیق `this` Context:** به صراحت `thisContext` را تعریف کنید تا از مشکلات رایج جاوااسکریپت جلوگیری شود.
|
|
252
|
+
- **🔄 آگاه از چرخه حیات (Lifecycle-Aware):** شامل متدهای `cancel()` و `flush()` برای کنترل کامل است که برای جلوگیری از نشت حافظه (memory leaks) در فریمورکهای کامپوننت-محور ضروری است.
|
|
253
|
+
- **🌐 Universal:** در هر محیط جاوااسکریپت، شامل مرورگرها، Node.js، Deno و Bun کار میکند.
|
|
254
|
+
- **🌳 قابل حذف در باندل نهایی (Tree-Shakable):** با استانداردهای مدرن ESM طراحی شده تا حجم نهایی باندل شما بهینه باشد.
|
|
255
|
+
|
|
256
|
+
## نصب
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
# با استفاده از npm
|
|
260
|
+
npm i @alwatr/debounce
|
|
261
|
+
|
|
262
|
+
# با استفاده از yarn
|
|
263
|
+
yarn add @alwatr/debounce
|
|
264
|
+
|
|
265
|
+
# با استفاده از pnpm
|
|
266
|
+
pnpm add @alwatr/debounce
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## مفاهیم اصلی
|
|
272
|
+
|
|
273
|
+
### دیبانس کردن (Debouncing) چیست؟
|
|
274
|
+
|
|
275
|
+
دیبانس کردن تکنیکی برای محدود کردن نرخ فراخوانی یک تابع است. این تکنیک تضمین میکند که کد شما **فقط یک بار** پس از یک دوره عدم فعالیت مشخص اجرا شود.
|
|
276
|
+
|
|
277
|
+
**مثال:** یک نوار جستجو را تصور کنید. شما نمیخواهید برای هر حرفی که کاربر تایپ میکند (`'ج'`, `'جس'`, `'جست'`, ...) یک درخواست API ارسال کنید. به جای آن، منتظر میمانید تا کاربر برای لحظهای **تایپ کردن را متوقف کند** (مثلاً ۳۰۰ میلیثانیه) و سپس یک درخواست واحد برای متن نهایی (`'جستجو'`) ارسال میکنید. این کار از درخواستهای شبکه غیرضروری جلوگیری کرده و عملکرد را بهبود میبخشد.
|
|
278
|
+
|
|
279
|
+
### لبه `leading` در مقابل `trailing`
|
|
280
|
+
|
|
281
|
+
زمانبندی اجرای تابع دیبانس شده توسط گزینههای `leading` و `trailing` کنترل میشود.
|
|
282
|
+
|
|
283
|
+
- **`trailing: true` (رفتار پیشفرض)**
|
|
284
|
+
تابع **پس از** اتمام دوره تأخیر، و به دنبال _آخرین_ فراخوانی، اجرا میشود. این رفتار کلاسیک دیبانس است و برای مواردی مانند نوار جستجو یا محاسبات ایدهآل است.
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
فراخوانی--فراخوانی--فراخوانی----(تأخیر)----> اجرا
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
- **`leading: true`**
|
|
291
|
+
تابع **بلافاصله** در _اولین_ فراخوانی اجرا میشود. فراخوانیهای بعدی در طول دوره تأخیر نادیده گرفته میشوند. این گزینه برای مواقعی که بازخورد فوری نیاز است (مانند کلیک روی دکمهای که نباید به صورت مکرر فشرده شود) مفید است.
|
|
292
|
+
|
|
293
|
+
```
|
|
294
|
+
اجرا <--فراخوانی--فراخوانی--فراخوانی----(تأخیر)----> (بدون اجرا)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
- **`leading: true` و `trailing: true`**
|
|
298
|
+
تابع هم در **اولین** فراخوانی و هم در **آخرین** فراخوانی که پس از دوره تأخیر رخ میدهد، اجرا میشود. این حالت برای سناریوهایی که به هر دو عمل فوری و نهایی نیاز دارند، کاربرد دارد.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## شروع سریع
|
|
303
|
+
|
|
304
|
+
سادهترین راه برای شروع، استفاده از تابع سازنده (factory function) `createDebouncer` است.
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
import {createDebouncer} from '@alwatr/debounce';
|
|
308
|
+
|
|
309
|
+
// ۱. یک نمونه دیبانسر بسازید
|
|
310
|
+
const debouncer = createDebouncer({
|
|
311
|
+
// تابعی که میخواهید دیبانس کنید
|
|
312
|
+
callback: (query: string) => {
|
|
313
|
+
console.log(`در حال جستجو برای: ${query}`);
|
|
314
|
+
},
|
|
315
|
+
// تأخیر بر حسب میلیثانیه
|
|
316
|
+
delay: 300,
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// ۲. آن را چندین بار فراخوانی کنید
|
|
320
|
+
console.log('کاربر در حال تایپ است...');
|
|
321
|
+
debouncer.trigger('ع');
|
|
322
|
+
debouncer.trigger('عل');
|
|
323
|
+
debouncer.trigger('علی');
|
|
324
|
+
|
|
325
|
+
// پس از ۳۰۰ میلیثانیه عدم فعالیت، در کنسول نمایش داده میشود:
|
|
326
|
+
// "در حال جستجو برای: علی"
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## مرجع API
|
|
330
|
+
|
|
331
|
+
### `createDebouncer(config)`
|
|
332
|
+
|
|
333
|
+
یک تابع سازنده که یک نمونه جدید از `Debouncer` ایجاد میکند. این روش توصیهشده برای ساخت دیبانسرها به منظور بهرهمندی از استنتاج نوع (type inference) بهتر است.
|
|
334
|
+
|
|
335
|
+
### `DebouncerConfig`
|
|
336
|
+
|
|
337
|
+
این آبجکت تنظیماتی است که به `createDebouncer` یا سازنده `Debouncer` پاس داده میشود.
|
|
338
|
+
|
|
339
|
+
| ویژگی | نوع | توضیحات | پیشفرض |
|
|
340
|
+
| :------------ | :---------------------- | :-------------------------------------------------------------------- | :---------- |
|
|
341
|
+
| `callback` | `F extends AnyFunction` | **(الزامی)** تابعی که باید دیبانس شود. | - |
|
|
342
|
+
| `delay` | `number` | **(الزامی)** تأخیر دیبانس بر حسب میلیثانیه. | - |
|
|
343
|
+
| `thisContext` | `ThisParameterType<F>` | کانتکست `this` برای callback. هنگام استفاده از متدهای کلاس ضروری است. | `undefined` |
|
|
344
|
+
| `leading` | `boolean` | اگر `true` باشد، تابع در لبه بالارونده (leading edge) اجرا میشود. | `false` |
|
|
345
|
+
| `trailing` | `boolean` | اگر `true` باشد، تابع در لبه پایینرونده (trailing edge) اجرا میشود. | `true` |
|
|
346
|
+
|
|
347
|
+
### نمونه `Debouncer`
|
|
348
|
+
|
|
349
|
+
یک نمونه از کلاس `Debouncer` که توسط `createDebouncer` برگردانده میشود.
|
|
350
|
+
|
|
351
|
+
#### پراپرتیها
|
|
352
|
+
|
|
353
|
+
- **`isPending`**: `boolean` (Getter)
|
|
354
|
+
اگر یک تابع دیبانس شده برای اجرا زمانبندی شده باشد، `true` برمیگرداند.
|
|
355
|
+
|
|
356
|
+
#### متدها
|
|
357
|
+
|
|
358
|
+
- **`trigger(...args: Parameters<F>): void`**
|
|
359
|
+
تایمر دیبانس را فعال میکند. هر فراخوانی، تایمر را ریست میکند. آرگومانهای پاس داده شده به این متد، به تابع `callback` ارسال میشوند.
|
|
360
|
+
|
|
361
|
+
- **`cancel(): void`**
|
|
362
|
+
هرگونه اجرای در حال انتظار را لغو کرده و وضعیت داخلی را پاک میکند. این متد برای جلوگیری از نشت حافظه بسیار حیاتی است.
|
|
363
|
+
|
|
364
|
+
- **`flush(): void`**
|
|
365
|
+
در صورت وجود، تابع در حال انتظار را بلافاصله و بدون توجه به تأخیر اجرا میکند. اگر هیچ فراخوانی در انتظار نباشد، کاری انجام نمیدهد.
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## استفاده پیشرفته و بهترین شیوهها
|
|
370
|
+
|
|
371
|
+
### **⚠️ مهم: مدیریت چرخه حیات و نشت حافظه**
|
|
372
|
+
|
|
373
|
+
در اپلیکیشنهای مدرن تکصفحهای (SPA) یا هر معماری مبتنی بر کامپوننت، کامپوننتها به طور مکرر ایجاد و نابود میشوند. اگر یک نمونه `Debouncer` در زمانی که کامپوننت مرتبط با آن نابود میشود فعال باشد، `setTimeout` داخلی آن میتواند یک ارجاع (reference) به کامپوننت را زنده نگه دارد و از پاک شدن آن توسط Garbage Collector جلوگیری کند. **این وضعیت یک نشت حافظه (Memory Leak) است.**
|
|
374
|
+
|
|
375
|
+
**راه حل:** همیشه متد `debouncer.cancel()` را در مرحله پاکسازی (cleanup) کامپوننت یا کلاس خود فراخوانی کنید.
|
|
376
|
+
|
|
377
|
+
#### مثال با یک کلاس ساده
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
class MyComponent {
|
|
381
|
+
private debouncer = createDebouncer({
|
|
382
|
+
callback: this.doSomething,
|
|
383
|
+
thisContext: this, // `this` را به درستی متصل کنید!
|
|
384
|
+
delay: 500,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
constructor() {
|
|
388
|
+
window.addEventListener('resize', this.debouncer.trigger);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
doSomething() {
|
|
392
|
+
console.log('اندازه پنجره تغییر کرد!', this);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// متد پاکسازی حیاتی
|
|
396
|
+
destroy() {
|
|
397
|
+
window.removeEventListener('resize', this.debouncer.trigger);
|
|
398
|
+
this.debouncer.cancel(); // از نشت حافظه جلوگیری میکند!
|
|
399
|
+
console.log('کامپوننت نابود شد و دیبانسر پاکسازی شد.');
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
#### مثال مفهومی در React
|
|
405
|
+
|
|
406
|
+
```jsx
|
|
407
|
+
import {useEffect, useMemo} from 'react';
|
|
408
|
+
import {createDebouncer} from '@alwatr/debounce';
|
|
409
|
+
|
|
410
|
+
function MyComponent() {
|
|
411
|
+
const debouncedApiCall = useMemo(
|
|
412
|
+
() =>
|
|
413
|
+
createDebouncer({
|
|
414
|
+
callback: (query) => fetch(`/api/search?q=${query}`),
|
|
415
|
+
delay: 300,
|
|
416
|
+
}),
|
|
417
|
+
[],
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
useEffect(() => {
|
|
421
|
+
// این تابع پاکسازی است.
|
|
422
|
+
// زمانی که کامپوننت unmount میشود، اجرا خواهد شد.
|
|
423
|
+
return () => {
|
|
424
|
+
debouncedApiCall.cancel(); // بسیار مهم!
|
|
425
|
+
};
|
|
426
|
+
}, [debouncedApiCall]);
|
|
427
|
+
|
|
428
|
+
return <input onChange={(e) => debouncedApiCall.trigger(e.target.value)} />;
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### استفاده با `thisContext`
|
|
433
|
+
|
|
434
|
+
زمانی که `callback` شما یک متد از یک کلاس است، `this` ممکن است کانتکست خود را از دست بدهد. برای اطمینان از اتصال صحیح، نمونه کلاس را به `thisContext` پاس دهید.
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
class ApiService {
|
|
438
|
+
private debouncer = createDebouncer({
|
|
439
|
+
callback: this.sendRequest,
|
|
440
|
+
thisContext: this, // تضمین میکند که `this` در داخل `sendRequest` همان `ApiService` است
|
|
441
|
+
delay: 500,
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
constructor(private endpoint: string) {}
|
|
445
|
+
|
|
446
|
+
public queueRequest(data: unknown) {
|
|
447
|
+
this.debouncer.trigger(data);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
private sendRequest(data: unknown) {
|
|
451
|
+
console.log(`در حال ارسال داده به ${this.endpoint}:`, data);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const service = new ApiService('/users');
|
|
456
|
+
service.queueRequest({name: 'user1'});
|
|
457
|
+
service.queueRequest({name: 'user2'}); // فقط آخرین درخواست ارسال خواهد شد
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
## مشارکت
|
|
461
|
+
|
|
462
|
+
از مشارکت شما استقبال میکنیم\! لطفاً یک issue باز کنید یا یک pull request ارسال نمایید.
|
|
463
|
+
|
|
464
|
+
## مجوز
|
|
465
|
+
|
|
466
|
+
این پروژه تحت مجوز **MPL-2.0** منتشر شده است.
|
|
467
|
+
|
|
468
|
+
\</div\>
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { DebouncerConfig } from './type.ts';
|
|
2
|
+
/**
|
|
3
|
+
* A powerful and type-safe Debouncer class.
|
|
4
|
+
*
|
|
5
|
+
* It encapsulates the debouncing logic, state, and provides a rich control API.
|
|
6
|
+
* Debouncing delays function execution until after a specified delay has passed since the last invocation.
|
|
7
|
+
* Useful for optimizing performance in scenarios like search inputs, resize events, or API calls.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const debouncer = new Debouncer({
|
|
12
|
+
* callback: (text: string) => console.log('Searching:', text),
|
|
13
|
+
* delay: 300,
|
|
14
|
+
* leading: false,
|
|
15
|
+
* trailing: true,
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* // Debounce search input
|
|
19
|
+
* debouncer.trigger('hello');
|
|
20
|
+
* debouncer.trigger('hello world'); // Only 'hello world' will log after 300ms
|
|
21
|
+
*
|
|
22
|
+
* // Advanced: With leading edge
|
|
23
|
+
* const leadingDebouncer = new Debouncer({
|
|
24
|
+
* callback: () => console.log('Immediate and delayed'),
|
|
25
|
+
* delay: 500,
|
|
26
|
+
* leading: true,
|
|
27
|
+
* trailing: true,
|
|
28
|
+
* });
|
|
29
|
+
* leadingDebouncer.trigger(); // Logs immediately, then again after 500ms if not cancelled
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare class Debouncer<F extends AnyFunction> {
|
|
33
|
+
private readonly config__;
|
|
34
|
+
private timerId__?;
|
|
35
|
+
private lastArgs__?;
|
|
36
|
+
constructor(config__: DebouncerConfig<F>);
|
|
37
|
+
/**
|
|
38
|
+
* Checks if there is a pending execution scheduled.
|
|
39
|
+
* Returns true if a timer is active, indicating a debounced call is waiting.
|
|
40
|
+
*/
|
|
41
|
+
get isPending(): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Triggers the debounced function with the stored `thisContext`.
|
|
44
|
+
* @param args The arguments to pass to the callback.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* const debouncer = new Debouncer({
|
|
49
|
+
* callback: (value: number) => console.log('Value:', value),
|
|
50
|
+
* delay: 500,
|
|
51
|
+
* });
|
|
52
|
+
* debouncer.trigger(42); // Logs after 500ms if not triggered again
|
|
53
|
+
*
|
|
54
|
+
* // Edge case: Rapid triggers only execute the last one
|
|
55
|
+
* debouncer.trigger(1);
|
|
56
|
+
* debouncer.trigger(2); // Only 2 will execute after delay
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
trigger(...args: Parameters<F>): void;
|
|
60
|
+
/**
|
|
61
|
+
* Cancels any pending debounced execution and cleans up internal state.
|
|
62
|
+
* Useful for stopping execution when the operation is no longer needed (e.g., component unmount).
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* const debouncer = new Debouncer({
|
|
67
|
+
* callback: () => console.log('Executed'),
|
|
68
|
+
* delay: 1000,
|
|
69
|
+
* });
|
|
70
|
+
* debouncer.trigger();
|
|
71
|
+
* debouncer.cancel(); // Prevents execution
|
|
72
|
+
*
|
|
73
|
+
* // Note: After cancel, isPending becomes false
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
cancel(): void;
|
|
77
|
+
/**
|
|
78
|
+
* Cleans up internal state by deleting timer and arguments.
|
|
79
|
+
*/
|
|
80
|
+
private cleanup__;
|
|
81
|
+
/**
|
|
82
|
+
* Immediately executes the pending function if one exists.
|
|
83
|
+
* Bypasses the delay and cleans up state. If no pending call, does nothing.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* const debouncer = new Debouncer({
|
|
88
|
+
* callback: () => console.log('Flushed'),
|
|
89
|
+
* delay: 1000,
|
|
90
|
+
* });
|
|
91
|
+
* debouncer.trigger();
|
|
92
|
+
* setTimeout(() => debouncer.flush(), 500); // Executes immediately
|
|
93
|
+
*
|
|
94
|
+
* // Edge case: Flush after cancel does nothing
|
|
95
|
+
* debouncer.cancel();
|
|
96
|
+
* debouncer.flush(); // No execution
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
flush(): void;
|
|
100
|
+
/**
|
|
101
|
+
* The core execution logic.
|
|
102
|
+
*/
|
|
103
|
+
private invoke__;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Factory function for creating a Debouncer instance for better type inference.
|
|
107
|
+
* @param config Configuration for the debouncer.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```typescript
|
|
111
|
+
* const debouncer = createDebouncer({
|
|
112
|
+
* callback: (text: string) => console.log('Searching:', text),
|
|
113
|
+
* delay: 300,
|
|
114
|
+
* leading: false,
|
|
115
|
+
* trailing: true,
|
|
116
|
+
* });
|
|
117
|
+
*
|
|
118
|
+
* // Debounce search input
|
|
119
|
+
* debouncer.trigger('hello');
|
|
120
|
+
* debouncer.trigger('hello world'); // Only 'hello world' will log after 300ms
|
|
121
|
+
*
|
|
122
|
+
* // With custom thisContext
|
|
123
|
+
* const obj = { log: (msg: string) => console.log('Obj:', msg) };
|
|
124
|
+
* const debouncerWithContext = createDebouncer({
|
|
125
|
+
* callback: obj.log,
|
|
126
|
+
* thisContext: obj,
|
|
127
|
+
* delay: 200,
|
|
128
|
+
* });
|
|
129
|
+
* debouncerWithContext.trigger('test'); // Logs 'Obj: test'
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
export declare function createDebouncer<F extends AnyFunction>(config: DebouncerConfig<F>): Debouncer<F>;
|
|
133
|
+
//# sourceMappingURL=debounce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debounce.d.ts","sourceRoot":"","sources":["../src/debounce.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,WAAW,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,SAAS,CAAC,CAAC,SAAS,WAAW;IAIvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAH5C,OAAO,CAAC,SAAS,CAAC,CAA0B;IAC5C,OAAO,CAAC,UAAU,CAAC,CAAgB;gBAEC,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC;IAIhE;;;OAGG;IACH,IAAW,SAAS,IAAI,OAAO,CAE9B;IAED;;;;;;;;;;;;;;;;OAgBG;IACI,OAAO,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI;IAoB5C;;;;;;;;;;;;;;;OAeG;IACI,MAAM,IAAI,IAAI;IAOrB;;OAEG;IACH,OAAO,CAAC,SAAS;IAKjB;;;;;;;;;;;;;;;;;OAiBG;IACI,KAAK,IAAI,IAAI;IAOpB;;OAEG;IACH,OAAO,CAAC,QAAQ;CAMjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,WAAW,EAAE,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAE/F"}
|