這篇文章主要介紹了在團隊項目開發中使用 TypeScript 的價值,重點講解了如何用 TypeScript 實現一個類型安全的防抖函數。從函數框架、參數添加、定時器邏輯到使用泛型優化、添加 cancel 方法和 JSDoc 注釋等步驟,詳細闡述了防抖函數的實現過程及優點,強調了其提升代碼安全性和健壯性的作用。
為什么要去使用 TypeScript ? 一直以來 TypeScript
的存在都備受爭議,很多人認為他加重了前端開發的負擔,特別是在它的嚴格類型系統和 JavaScript
的靈活性之間的矛盾上引發了不少討論。
支持者認為 TypeScript
提供了強類型檢查、豐富的 IDE 支持和更好的代碼重構能力,從而提高了大型項目的代碼質量和可維護性。
然而,也有很多開發者認為 TypeScript
加重了開發負擔,帶來了不必要的復雜性 ,尤其是在小型項目或快速開發場景中,它的嚴格類型系統可能顯得過于繁瑣,限制了 JavaScript
本身的動態和自由特性
但是隨著項目規模的增大和團隊協作的復雜性增加,TypeScript
的優勢也更加明顯。因為你不可能指望團隊中所有人的知識層次和開發習慣都達到同一水準!你也不可能保證團隊中的其他人都能夠完全正確的使用你封裝的組件、函數!
在大型項目中我們往往會封裝到很多工具函數、組件等等,我們不可能在使用到組件時跑去看這個組件的實現邏輯,而 TypeScript
的類型提示正好彌補了這一點。通過明確的類型注解,TypeScript
可以在代碼中直接提示每個組件的輸入輸出、參數類型和預期結果,讓開發者只需在 IDE 中懸停或查看提示信息,就能了解組件的用途和使用方式,而不需要翻閱具體實現邏輯。
這時你可能會說,使用 JSDoc
也能夠實現類似的效果。的確,JSDoc
可以通過注釋的形式對函數、參數、返回值等信息進行詳細描述,甚至可以生成文檔。
然而,JSDoc
依賴于開發者的自覺維護 ,且其檢查和提示能力遠不如 TypeScript
強大和全面。TypeScript
的類型系統是在編譯階段強制執行的 ,這意味著所有類型定義都是真正的 “硬性約束” ,能在代碼運行前捕獲錯誤,而不僅僅是提示。
在實際開發中,JSDoc
的確能讓我們知道參數類型,但它只是一種 “約定” ,而不是真正的約束。這意味著,如果同事在使用工具函數時不小心寫錯了類型,比如傳了字符串而不是數字,JSDoc
只能通過注釋告訴你正確的使用方法,卻無法在你出錯時立即給出警告。
然而在 TypeScript
中,類型系統會在代碼編寫階段實時檢查 。比如,你定義的函數要求傳入數字類型的參數,如果有人傳入了字符串,IDE 立刻會報錯提醒你,防止錯誤進一步傳播。
所以,TypeScript 的價值就在于它提供了一層代碼保護,讓代碼有了“硬約束”,團隊在開發過程中更加節省心智負擔,顯著提升開發體驗和生產力,少出錯、更高效。
接下來我們來使用 TypeScript
寫一個基礎的防抖函數作為示例。通過類型定義和參數注解,我們不僅能讓防抖函數更加通用且類型安全,還能充分利用 TypeScript
的類型檢查優勢,從而提高代碼的可讀性和可維護性。
這樣的實現方式將有效地降低潛在的運行時錯誤,特別是在大型項目中,可以使團隊成員之間的協作能夠更加順暢,并且避免一些低級問題。
?
功能點講解 防抖函數的主要功能是:在指定的延遲時間內,如果函數多次調用,只有最后一次調用會生效 。這一功能尤其適合優化用戶輸入等高頻事件。
防抖函數的核心功能 函數執行的延遲控制 :函數調用后不立即執行,而是等待一段時間。如果在等待期間再次調用函數,之前的等待會被取消,重新計時。
立即執行選項 :有時我們希望函數在第一次調用時立即執行,然后在延遲時間內避免再次調用。
取消功能 :我們還希望在某些情況下手動取消延遲執行的函數,比如當頁面卸載或需要重新初始化時。
第一步:編寫函數框架 在開始封裝防抖函數之前,我們首先應該想到的就是要寫一個函數,假設這個函數名叫 debounce
。我們先創建它的基本框架:
typescript 代碼解讀 復制代碼function debounce () { // 函數的邏輯將在這里編寫 }
這一步非常簡單,先定義一個空函數,這個函數就是我們的防抖函數。在后續步驟中,我們會逐步向這個函數中添加功能。
第二步:添加基本的參數 防抖函數的第一個功能是控制某個函數的執行 ,因此,我們需要傳遞一個需要防抖的函數 。其次,防抖功能依賴于一個延遲時間 ,這意味著我們還需要添加一個用于設置延遲的參數。
讓我們擴展一下 debounce
函數,為它添加兩個基本的參數:
func
:需要防抖的目標函數。
duration
:防抖的延遲時間,單位是毫秒。
typescript 代碼解讀 復制代碼function debounce (func: Function , duration: number ) { // 函數的邏輯將在這里編寫 }
第三步:為防抖功能引入定時器邏輯 防抖的核心邏輯就是通過定時器 (setTimeout
),讓函數執行延后。那么我們需要用一個變量來保存這個定時器,以便在函數多次調用時可以取消之前的定時器。
typescript 代碼解讀 復制代碼function debounce (func: Function , duration: number ) { let timer : ReturnType <typeof setTimeout > | null = null ; // 定時器變量 }
let timer: ReturnType<typeof setTimeout> | null = null
:我們使用一個變量 timer
來存儲定時器的返回值。
clearTimeout(timer)
:每次調用防抖函數時,都會清除之前的定時器,這樣就保證了函數不會被立即執行,直到等待時間結束。
setTimeout
:在指定的延遲時間后執行傳入的目標函數 func
,并傳遞原始參數。
為什么寫成了 ReturnType<typeof setTimeout> | null
這樣的類型 ?
在 JavaScript
中,setTimeout
是一個內置函數,用來設置一個延遲執行的任務。它的基本語法如下:
typescript 代碼解讀 復制代碼let id = setTimeout (() => { console .log ("Hello, world!" ); }, 1000 );
setTimeout
返回一個定時器 ID (在瀏覽器中是一個數字),這個 ID 用來唯一標識這個定時器。如果你想取消定時器,你可以使用 clearTimeout(id)
,其中 id
就是這個返回的定時器 ID。
ReturnType<T>
是 TypeScript 提供的一個工具類型 ,它的作用是幫助我們獲取某個函數類型的返回值類型 。我們通過泛型 T
來傳入一個函數類型 ,然后 ReturnType<T>
就會返回這個函數的返回值類型。在這里我們可以用它來獲取 setTimeout
函數的返回類型。
為什么需要使用 ReturnType<typeof setTimeout>
? 由于不同的 JavaScript
運行環境中,setTimeout
的返回值類型是不同的:
為了兼容不同的環境,我們需要用 ReturnType<typeof setTimeout>
來動態獲取 setTimeout
返回的類型,而不是手動指定類型(比如 number
或 Timeout
)。
typescript 代碼解讀 復制代碼let timer : ReturnType <typeof setTimeout >;
這里 ReturnType<typeof setTimeout>
表示我們根據 setTimeout
的返回值類型自動推導出變量 timer
的類型,不管是數字(瀏覽器)還是對象(Node.js),TypeScript 會自動處理。為什么需要設置聯合類型 | null
? 在我們的防抖函數實現中,定時器 timer
并不是一開始就設置好的。我們需要在每次調用防抖函數時動態設置定時器 ,所以初始狀態下,timer
的值應該是 null
。 使用 | null
表示聯合類型 ,它允許 timer
變量既可以是 setTimeout
返回的值,也可以是 null
,表示目前還沒有設置定時器。
typescript 代碼解讀 復制代碼let timer : ReturnType <typeof setTimeout > | null = null ;
第四步:返回一個新函數 在防抖函數 debounce
中,我們希望當它被調用時,返回一個新的函數。這是防抖函數的核心機制,因為每次調用返回的新函數,實際上是在控制目標函數 func
的執行。 具體的想法是這樣的:我們并不直接執行傳入的目標函數 func
,而是返回一個新函數,這個新函數在被調用時會受到防抖的控制。
因此,我們要修改 debounce
函數,使它返回一個新的函數,真正控制 func
的執行時機。
typescript 代碼解讀 復制代碼function debounce (func: Function , duration: number ) { let timer : ReturnType <typeof setTimeout > | null = null ; // 定時器變量 return function () { // 防抖邏輯將在這里編寫 }; }
第五步:清除之前的定時器 為了實現防抖功能,每次調用返回的新函數時,我們需要先清除之前的定時器。如果之前有一個定時器在等待執行目標函數,我們應該將其取消,然后重新設置一個新的定時器。 這個步驟的關鍵就是使用 clearTimeout(timer)
。
typescript 代碼解讀 復制代碼function debounce (func: Function , duration: number ) { let timer : ReturnType <typeof setTimeout > | null = null ; // 定時器變量 return function () { if (timer) { clearTimeout (timer); // 清除之前的定時器 } // 下面將設置新的定時器 }; }
第六步:設置新的定時器 現在我們需要在每次調用返回的新函數時,重新設置一個新的定時器 ,讓它在指定的延遲時間 duration
之后執行目標函數 func
。 這時候就要使用 setTimeout
來設置定時器,并在延遲時間后執行目標函數。
typescript 代碼解讀 復制代碼function debounce (func: Function , duration: number ) { let timer : ReturnType <typeof setTimeout > | null = null ; // 定時器變量 return function () { if (timer) { clearTimeout (timer); // 清除之前的定時器 } timer = setTimeout (() => { func (); // 延遲后調用目標函數 }, duration); }; }
setTimeout
:我們使用 setTimeout
來設置一個新的定時器,定時器將在 duration
毫秒后執行傳入的目標函數 func
。
func()
:這是目標函數的實際執行點。定時器到達延遲時間時,它會執行目標函數 func
。
timer = setTimeout(...)
:我們將定時器的 ID 存儲在 timer
變量中,以便后續可以使用 clearTimeout(timer)
來清除定時器。
第七步:支持參數傳遞 接下來是讓這個防抖函數能夠接受參數,并將這些參數傳遞給目標函數 func
。 為了實現這個功能,我們需要用到 ...args
來捕獲所有傳入的參數,并在執行目標函數時將這些參數傳遞過去。
typescript 代碼解讀 復制代碼function debounce (func: Function , duration: number ) { let timer : ReturnType <typeof setTimeout > | null = null ; // 定時器變量 return function (...args: any [] ) { // 接收傳入的參數 if (timer) { clearTimeout (timer); // 清除之前的定時器 } timer = setTimeout (() => { func (...args); // 延遲后調用目標函數,并傳遞參數 }, duration); }; }
到這里,我們一個基本的防抖函數的實現。這個防抖函數實現了以下基本功能:
函數執行的延遲控制 :每次調用時,都重新設置定時器,確保函數不會立即執行,而是在延遲結束后才執行。
多參數支持 :通過 ...args
,防抖函數能夠接收多個參數,并將它們傳遞給目標函數。
清除之前的定時器 :在每次調用時,如果定時器已經存在,先清除之前的定時器,確保只有最后一次調用才會生效。
但是,這樣就完了嗎? 在當前的實現中,debounce
函數的定義是 debounce(func: Function, duration: number)
,其中 func: Function
用來表示目標函數。這種定義雖然可以工作,但它存在明顯的缺陷和不足之處,尤其是在 TypeScript 強調類型安全的情況下。
缺陷 1:缺乏參數類型檢查 Function
是一種非常寬泛的類型,它允許目標函數接收任何類型、任意數量的參數。因此定義目標函數 func
為 Function
類型意味著 TypeScript 無法對目標函數的 參數類型進行任何檢查。
typescript 代碼解讀 復制代碼const debounced = debounce ((a: number , b: number ) => { console .log (a + b); }, 200 ); debounced ("hello" , "world" ); // 這里不會報錯,參數類型不匹配,但仍會被調用
在這個例子中,我們定義了一個目標函數,期望它接受兩個數字類型 的參數,但在實際調用時卻傳入了兩個字符串。 這種情況下 TypeScript 不會提示任何錯誤,因為 Function
類型沒有對參數類型進行限制。這種類型檢查的缺失可能導致運行時錯誤或者邏輯上的錯誤。
缺陷 2:返回值類型不安全 同樣,定義 func
為 Function
類型時,TypeScript 無法推斷目標函數的返回值類型 。這意味著防抖函數不能保證目標函數的返回值是符合預期的類型,可能導致返回值在其他地方被錯誤使用。
typescript 代碼解讀 復制代碼const debounced = debounce (() => { return "result" ; }, 200 ); const result = debounced (); // TypeScript 不知道返回值類型,認為是 undefined
在這個例子中,雖然目標函數明確返回了一個字符串 "result"
,但 debounced
函數的返回值類型未被推斷出來,因此 TypeScript 會認為它的返回值是 void
或 undefined
,即使目標函數實際上返回了 string
。
缺陷 3:缺乏目標函數的簽名限制 由于 Function
類型允許任何形式的函數,因此 TypeScript 也無法檢查目標函數的參數個數和類型是否匹配。這種情況下,如果防抖函數返回的新函數接收了錯誤數量或類型的參數,可能導致函數行為異常或意外的運行時錯誤。
typescript 代碼解讀 復制代碼const debounced = debounce ((a: number ) => { console .log (a); }, 200 ); debounced (1 , 2 , 3 ); // TypeScript 不會報錯,但多余的參數不會被使用
雖然目標函數只期望接收一個參數,但在調用時傳入了多個參數。TypeScript 不會進行任何警告或報錯,因為 Function
類型允許這種寬泛的調用,這可能會導致開發者誤以為這些參數被使用。
總結 func: Function
的缺陷 缺乏參數類型檢查 :任何數量、任意類型的參數都可以傳遞給目標函數 ,導致潛在的參數類型錯誤。
返回值類型不安全 :目標函數的返回值類型無法被推斷 ,導致 TypeScript 無法確保返回值的類型正確。
函數簽名不受限制 :沒有對目標函數的參數個數和類型進行檢查 ,容易導致邏輯錯誤或參數使用不當。
這些缺陷使得代碼在類型安全性和健壯性上存在不足,可能導致運行時錯誤或者隱藏的邏輯漏洞。
下一步的改進 為了解決這些缺陷,我們可以通過泛型 的方式為目標函數添加類型限制,確保目標函數的參數和返回值類型都能被準確地推斷和檢查。這會是我們接下來要進行的優化。
第八步:使用泛型優化 為了克服 func: Function
帶來的缺陷,我們可以通過 泛型 來優化防抖函數的類型定義,確保目標函數的參數和返回值都能在編譯時進行類型檢查。使用泛型不僅可以解決參數類型和返回值類型的檢查問題,還可以提升代碼的靈活性和安全性。
如何使用泛型進行優化? 我們將通過引入兩個泛型參數來改進防抖函數的類型定義:
A
:表示目標函數的參數類型 ,可以是任意類型和數量的參數,確保防抖函數在接收參數時能進行類型檢查。
R
:表示目標函數的返回值類型 ,確保防抖函數返回的值與目標函數一致。
typescript 代碼解讀 復制代碼function debounce<A extends any [], R>( func : (...args: A ) => R, // 使用泛型 A 表示參數,R 表示返回值類型 duration : number // 延遲時間,以毫秒為單位 ): (...args: A ) => R { // 返回新函數,參數類型與目標函數相同,返回值類型為 R let timer : ReturnType <typeof setTimeout > | null = null ; // 定時器變量 let lastResult : R; // 存儲目標函數的返回值 return function (...args: A ): R { // 返回的新函數,參數類型由 A 推斷 if (timer) { clearTimeout (timer); // 清除之前的定時器 } timer = setTimeout (() => { lastResult = func (...args); // 延遲后調用目標函數,并存儲返回值 }, duration); return lastResult; // 返回上一次執行的結果,如果尚未執行則返回 undefined }; }
A extends any[]
:A
表示目標函數的參數類型,A
是一個數組類型,能夠適應目標函數接收多個參數的場景。通過泛型,防抖函數能夠根據目標函數的簽名推斷出參數類型并進行檢查。
R
:R
表示目標函數的返回值類型,防抖函數能夠確保返回值類型與目標函數一致。如果目標函數返回值類型為 string
,防抖函數也會返回 string
,這樣可以防止返回值類型不匹配。
lastResult
:用來存儲目標函數的最后一次返回值。每次調用目標函數時會更新 lastResult
,并在調用時返回上一次執行的結果,確保防抖函數返回正確的返回值。
泛型優化后的優點 :
類型安全的參數傳遞 : 通過泛型 A
,防抖函數可以根據目標函數的簽名進行類型檢查,確保傳入的參數與目標函數一致,避免參數類型錯誤。
typescript 代碼解讀 復制代碼const debounced1 = debounce ((a: number , b: string ) => { console .log (a, b); }, 300 ); debounced1 (42 , "hello" ); // 正確,參數類型匹配 debounced1 ("42" , 42 ); // 錯誤,類型不匹配
返回值類型安全 : 泛型 R
確保了防抖函數的返回值與目標函數的返回值類型一致,防止不匹配的類型被返回。
typescript 代碼解讀 復制代碼const debounced = debounce (() => { return "result" ; }, 200 ); const result = debounced (); // 返回值為 string console .log (result); // 輸出 "result"
支持多參數傳遞 : 泛型 A
表示參數類型數組,這意味著目標函數可以接收多個參數,防抖函數會將這些參數正確傳遞給目標函數。而如果防抖函數返回的新函數接收了錯誤數量或類型的參數,會直接報錯提示。
typescript 代碼解讀 復制代碼const debounced = debounce ((name: string , age: number ) => { return `${name} is ${age} years old.` ; }, 300 ); const result = debounced ("Alice" , 30 ); console .log (result); // 輸出 "Alice is 30 years old."
第九步:添加 cancel
方法并處理返回值類型 在前面的步驟中,我們已經實現了一個可以延遲執行的防抖函數,并且支持參數傳遞和返回目標函數的結果。 但是,由于防抖函數的執行是異步延遲 的,因此在初次調用時,防抖函數可能無法立即返回結果 。因此函數的返回值我們需要使用 undefined
來表示目標函數的返回結果可能出現還沒生成的情況。 除此之外,我們還要為防抖函數添加一個 cancel
方法,用于手動取消防抖的延遲執行。為什么需要 cancel
方法? 在一些場景下,可能需要手動取消防抖操作,例如:
為了解決這些需求,cancel
方法可以幫助我們在定時器還未觸發時,清除定時器并停止目標函數的執行。
typescript 代碼解讀 復制代碼// 定義帶有 cancel 方法的防抖函數類型 type DebouncedFunction <A extends any [], R> = { (...args : A): R | undefined ; // 防抖函數本身,返回值可能為 R 或 undefined cancel : () => void ; // `cancel` 方法,用于手動清除防抖 }; // 實現防抖函數 function debounce<A extends any [], R>( func : (...args: A ) => R, // 泛型 A 表示參數類型,R 表示返回值類型 duration : number // 延遲時間 ): DebouncedFunction <A, R> { // 返回帶有 cancel 方法的防抖函數 let timer : ReturnType <typeof setTimeout > | null = null ; // 定時器變量 let lastResult : R | undefined ; // 用于存儲目標函數的返回值 // 防抖邏輯的核心函數 const debouncedFn = function (...args: A ): R | undefined { if (timer) { clearTimeout (timer); // 清除之前的定時器 } // 設置新的定時器 timer = setTimeout (() => { lastResult = func (...args); // 延遲后執行目標函數,并存儲返回值 }, duration); // 返回上一次的結果或 undefined return lastResult; }; // 添加 `cancel` 方法,用于手動取消防抖 debouncedFn.cancel = function () { if (timer) { clearTimeout (timer); // 清除定時器 timer = null ; // 重置定時器 } }; return debouncedFn; // 返回帶有 `cancel` 方法的防抖函數 }
返回值類型 R | undefined
:
R
:代表目標函數的返回值類型,例如 number
或 string
。
undefined
:在防抖函數的首次調用或目標函數尚未執行時,返回 undefined
,表示結果尚未生成。
lastResult
用于存儲目標函數上一次執行的結果,防抖函數在每次調用時會返回該結果,或者在尚未執行時返回 undefined
。
cancel
方法 :
讓我們來看一個具體的使用示例,展示如何使用防抖函數,并在需要時手動取消操作。
typescript 代碼解讀 復制代碼// 定義一個簡單的目標函數 const debouncedLog = debounce ((message: string ) => { console .log (message); return message; }, 300 ); // 第一次調用防抖函數,目標函數將在 300 毫秒后執行 debouncedLog ("Hello" ); // 如果不取消,300ms 后會輸出 "Hello" // 手動取消防抖,目標函數不會執行 debouncedLog.cancel ();
在這個示例中:
調用 debouncedLog("Hello")
:會啟動一個 300 毫秒的延遲執行,目標函數計劃在 300 毫秒后執行,并輸出 "Hello"
。
調用 debouncedLog.cancel()
:會清除定時器,目標函數不會執行,避免了不必要的操作。
第十步:將防抖函數作為工具函數單獨放在一個 ts
文件中并添加 JSDoc 注釋 在編寫好防抖函數之后,下一步是將其作為一個工具函數放入單獨的 .ts
文件中,以便在項目中重復使用。同時,我們可以為函數添加詳細的 JSDoc 注釋 ,方便使用者了解函數的作用、參數、返回值及用法。
1. 將防抖函數放入單獨的文件 首先,我們可以創建一個名為 debounce.ts
的文件,并將防抖函數的代碼放在其中。
typescript 代碼解讀 復制代碼// debounce.ts export type DebouncedFunction <A extends any [], R> = { (...args : A): R | undefined ; // 防抖函數本身,返回值可能為 R 或 undefined cancel : () => void ; // `cancel` 方法,用于手動清除防抖 }; /** * 創建一個防抖函數,確保在最后一次調用后,目標函數只會在指定的延遲時間后執行。 * 防抖函數可以防止某個函數被頻繁調用,例如用戶輸入事件、滾動事件或窗口調整大小等場景。 * * @template A - 函數接受的參數類型。 * @template R - 函數的返回值類型。 * @param {(...args: A) => R } func - 需要防抖的目標函數。該函數將在延遲時間后執行。 * @param {number } duration - 延遲時間(以毫秒為單位)。在這個時間內,如果再次調用函數,將重新計時。 * @returns {DebouncedFunction<A, R> } 一個防抖后的函數,該函數包括一個 `cancel` 方法用于清除防抖。 * * @example * const debouncedLog = debounce((message: string) => { * console.log(message); * return message; * }, 300); * * debouncedLog("Hello"); // 300ms 后輸出 "Hello" * debouncedLog.cancel(); // 取消防抖,函數不會執行 */ export function debounce<A extends any [], R>( func : (...args: A ) => R, duration : number ): DebouncedFunction <A, R> { let timer : ReturnType <typeof setTimeout > | null = null ; // 定時器變量 let lastResult : R | undefined ; // 存儲目標函數的返回值 const debouncedFn = function (...args: A ): R | undefined { if (timer) { clearTimeout (timer); // 清除之前的定時器 } timer = setTimeout (() => { lastResult = func (...args); // 延遲后執行目標函數,并存儲返回值 }, duration); return lastResult; // 返回上次執行的結果,如果尚未執行則返回 undefined }; debouncedFn.cancel = function () { if (timer) { clearTimeout (timer); // 清除定時器,防止目標函數被執行 timer = null ; // 重置定時器 } }; return debouncedFn; }
2. 詳細的 JSDoc 注釋說明 通過添加 JSDoc 注釋,能夠為函數使用者提供清晰的文檔信息,說明防抖函數的功能、參數類型、返回值類型,以及如何使用它。JSDoc 注釋的結構說明 :
@template A, R
:說明泛型 A
是函數接受的參數類型,R
是目標函數的返回值類型。
@param
:解釋函數的輸入參數,說明 func
是目標函數,duration
是防抖的延遲時間。
@returns
:說明返回值是一個帶有 cancel
方法的防抖函數,函數返回值類型是 R | undefined
。
@example
:為函數提供示例,展示防抖函數的典型用法,包括取消防抖操作。
使用 JSDoc 生成文檔 通過在 .ts
文件中添加 JSDoc 注釋,可以借助 TypeScript 編輯器或 IDE(如 VSCode/Webstorm)自動生成代碼提示和函數文檔說明,提升開發體驗。 例如,當開發者在使用 debounce
函數時,可以自動看到函數的說明和參數類型提示:
回顧:泛型防抖函數的最終效果 通過前面各個步驟的優化,我們已經構建了一個類型安全的防抖函數,結合泛型實現了以下關鍵功能:
類型安全的參數傳遞 : 通過泛型 A
,防抖函數能夠根據目標函數的簽名進行參數類型檢查,確保傳入的參數與目標函數的類型一致。如果傳入的參數類型不匹配,TypeScript 將在編譯時報錯,避免運行時的潛在錯誤。
typescript 代碼解讀 復制代碼const debounced1 = debounce ((a: number , b: string ) => { console .log (a, b); }, 300 ); debounced1 (42 , "hello" ); // 正確,參數類型匹配 debounced1 ("42" , 42 ); // 錯誤,類型不匹配
在上面的例子中,TypeScript 會檢查參數類型,確保傳入的參數符合預期的類型。錯誤的參數類型會被及時捕捉。
返回值類型安全 : 泛型 R
確保防抖函數的返回值與目標函數的返回值類型保持一致。TypeScript 可以根據目標函數的返回值類型推斷防抖函數的返回值,防止不匹配的類型被返回。
typescript 代碼解讀 復制代碼const debounced = debounce (() => { return "result" ; }, 200 ); const result = debounced (); // 返回值為 string console .log (result); // 輸出 "result"
在這個例子中,debounce
返回的防抖函數的返回值類型為 string
或者 undefind
,因為在防抖函數的實現中,目標函數是延遲執行的,因此在初次調用 或在延遲期間 ,debounced
函數返回的結果可能尚未生成,與目標函數的返回值類型預期一致。
支持多參數傳遞 : 泛型 A
表示目標函數的參數類型數組,這意味著防抖函數可以正確傳遞多個參數,并確保類型安全。如果傳入了錯誤數量或類型的參數,TypeScript 會提示開發者進行修正。
typescript 代碼解讀 復制代碼const debounced = debounce ((name: string , age: number ) => {return `${name} is ${age} years old.` ; }, 300 ); const result = debounced ("Alice" , 30 );console .log (result); // 輸出 "Alice is 30 years old."
在這個例子中,防抖函數正確地將多個參數傳遞給目標函數,并輸出目標函數的正確返回值。傳入的參數數量或類型不正確時,TypeScript 會發出報錯提示。
總結 至此,我 們完整實現并優化了一個類型安全的防抖函數,并通過泛型確保參數和返回值的類型安全。此外,我們還詳細講解了如何為防抖函數添加 cancel
方法,并處理延遲執行的返回值 R | undefined
。最后,我們將防抖函數封裝在一個單獨的 TypeScript 文件中,并為其添加了 JSDoc 注釋,使其成為一個可復用的工具函數。
通過這種方式,防抖函數不僅功能強大,還能在編譯時提供類型檢查,減少運行時的潛在錯誤。TypeScript
的類型系統幫助我們提升了代碼的安全性和健壯性。 最后,我們給出完整的的代碼如下:
// debounce.ts
export type DebouncedFunction<A extends any[], R> = {
(...args: A): R | undefined; // 防抖函數本身,返回值可能為 R 或 undefined
cancel: () => void; // `cancel` 方法,用于手動清除防抖
};
/**
* 創建一個防抖函數,確保在最后一次調用后,目標函數只會在指定的延遲時間后執行。
* 防抖函數可以防止某個函數被頻繁調用,例如用戶輸入事件、滾動事件或窗口調整大小等場景。
*
* @template A - 函數接受的參數類型。
* @template R - 函數的返回值類型。
* @param {(...args: A) => R} func - 需要防抖的目標函數。該函數將在延遲時間后執行。
* @param {number} duration - 延遲時間(以毫秒為單位)。在這個時間內,如果再次調用函數,將重新計時。
* @returns {DebouncedFunction<A, R>} 一個防抖后的函數,該函數包括一個 `cancel` 方法用于清除防抖。
*
* @example
* const debouncedLog = debounce((message: string) => {
* console.log(message);
* return message;
* }, 300);
*
* debouncedLog("Hello"); // 300ms 后輸出 "Hello"
* debouncedLog.cancel(); // 取消防抖,函數不會執行
*/
export function debounce<A extends any[], R>(
func: (...args: A) => R,
duration: number
): DebouncedFunction<A, R> {
let timer: ReturnType<typeof setTimeout> | null = null; // 定時器變量
let lastResult: R | undefined; // 存儲目標函數的返回值
const debouncedFn = function (...args: A): R | undefined {
if (timer) {
clearTimeout(timer); // 清除之前的定時器
}
timer = setTimeout(() => {
lastResult = func(...args); // 延遲后執行目標函數,并存儲返回值
}, duration);
return lastResult; // 返回上次執行的結果,如果尚未執行則返回 undefined
};
debouncedFn.cancel = function () {
if (timer) {
clearTimeout(timer); // 清除定時器,防止目標函數被執行
timer = null; // 重置定時器
}
};
return debouncedFn;
}
該文章在 2024/11/18 17:52:59 編輯過