diff --git a/README.md b/README.md index bf28a36..d37b94f 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ const copied = await commonUtil.copyToClipboard("Hello, World!"); // true if suc const encoded = commonUtil.encodeBase64("Hello 한글!"); // Base64 encoded string const decoded = commonUtil.decodeBase64(encoded); // "Hello 한글!" const debouncedFn = commonUtil.debounce(() => console.log("Called!"), 300); // Debounced function +const throttledFn = commonUtil.throttle(() => console.log("Throttled!"), 300); // Throttled function // Storage commonUtil.storage.set("user", { id: 1, name: "John" }); // Stores object in localStorage @@ -180,6 +181,7 @@ storage.set("data", { key: "value" }); - `encodeBase64(str: string, options?: { convertSpecialChars?: boolean }): string` - Encodes a string to Base64 format with optional special character handling - `decodeBase64(str: string, options?: { convertSpecialChars?: boolean }): string` - Decodes a Base64 string back to original text with optional special character handling - `debounce(fn: T, delay?: number): (...args: Parameters) => void` - Creates a debounced function that delays execution until after a specified delay (default 300ms) has passed since its last invocation +- `throttle(fn: T, limit?: number): (...args: Parameters) => void` - Creates a throttled function that only executes at most once per specified time interval (default 300ms), ignoring subsequent calls within the limit - `storage.set(key: string, value: T): void` - Stores a value in localStorage with automatic JSON serialization. Supports objects, arrays, and primitive types. Safe for SSR environments. - `storage.get(key: string): T | null` - Retrieves a value from localStorage with automatic JSON parsing. Returns null if key doesn't exist or parsing fails. Type-safe with generic support. - `storage.remove(key: string): void` - Removes a specific item from localStorage. Safe for SSR environments. diff --git a/package/commonUtil/index.ts b/package/commonUtil/index.ts index 1235d4c..a163132 100644 --- a/package/commonUtil/index.ts +++ b/package/commonUtil/index.ts @@ -5,4 +5,5 @@ export { default as copyToClipboard } from "./copyToClipboard"; export { default as encodeBase64 } from "./encodeBase64"; export { default as decodeBase64 } from "./decodeBase64"; export { default as debounce } from "./debounce"; +export { default as throttle } from "./throttle"; export { default as storage } from "./storage"; diff --git a/package/commonUtil/throttle/index.test.ts b/package/commonUtil/throttle/index.test.ts new file mode 100644 index 0000000..309ea99 --- /dev/null +++ b/package/commonUtil/throttle/index.test.ts @@ -0,0 +1,41 @@ +import { afterEach, describe, expect, test, vi } from "vitest"; +import throttle from "."; + +describe("throttle 유틸 함수 테스트", () => { + afterEach(() => { + vi.useRealTimers(); + vi.restoreAllMocks(); + }); + test("함수가 지정된 시간 (300ms) 간격으로만 실행되어야 한다.", () => { + const fn = vi.fn(); + vi.useFakeTimers(); + const throttleFn = throttle(fn, 300); + + throttleFn(); + expect(fn).toHaveBeenCalledTimes(1); + + throttleFn(); + vi.advanceTimersByTime(299); + throttleFn(); + expect(fn).toHaveBeenCalledTimes(1); + + vi.advanceTimersByTime(1); + throttleFn(); + expect(fn).toHaveBeenCalledTimes(2); + }); + test("여러 번 호출했을 경우에는 지정된 시간 간격으로만 이벤트가 실행되어야 한다.", () => { + const fn = vi.fn(); + vi.useFakeTimers(); + const throttled = throttle(fn, 300); + + throttled({ name: "현우" }); + vi.advanceTimersByTime(100); + throttled({ name: "승준" }); + vi.advanceTimersByTime(100); + throttled({ name: "철수" }); + + vi.advanceTimersByTime(300); + expect(fn).toHaveBeenCalledTimes(1); + expect(fn).toHaveBeenNthCalledWith(1, { name: "현우" }); + }); +}); diff --git a/package/commonUtil/throttle/index.ts b/package/commonUtil/throttle/index.ts new file mode 100644 index 0000000..02cdfb0 --- /dev/null +++ b/package/commonUtil/throttle/index.ts @@ -0,0 +1,13 @@ +export default function throttle void>( + fn: T, + limit: number = 300 +) { + let isThrottled: boolean = false; + return (...args: Parameters) => { + if (!isThrottled) { + fn(...args); + isThrottled = true; + setTimeout(() => (isThrottled = false), limit); + } + }; +}