如何在 TypeScript 中使用函數(shù)
點擊上方?前端Q,關注公眾號
回復加群,加入前端Q技術交流群
英文 | https://www.digitalocean.com/community/tutorials/how-to-use-functions-in-typescript
翻譯 | 楊小愛
function sum(a, b) {return a + b;}
在本例中,sum 是函數(shù)的名稱,(a, b) 是參數(shù),{return a + b;} 是函數(shù)體。
在 TypeScript 中創(chuàng)建函數(shù)的語法是相同的,除了一個主要的補充:我們可以讓編譯器知道每個參數(shù)或參數(shù)應該具有什么類型。以下代碼塊顯示了一般語法,突出顯示了類型聲明:
function functionName(param1: Param1Type, param2: Param2Type): ReturnType {// ... body of the function}
使用此語法,我們可以將類型添加到前面顯示的 sum 函數(shù)的參數(shù):
function sum(a: number, b: number) {return a + b;}
這確保 a 和 b 是數(shù)值。
我們還可以添加返回值的類型:
function sum(a: number, b: number): number {return a + b;}
現(xiàn)在 TypeScript 將期望 sum 函數(shù)返回一個數(shù)字值。如果我們使用一些參數(shù)調(diào)用函數(shù)并將結果值存儲在名為 result 的變量中:
const result = sum(1, 2);結果變量將具有類型編號。如果我們正在使用 TypeScript 游樂場或使用完全支持 TypeScript 的文本編輯器,將光標懸停在 result 上將顯示 const result: number,表明 TypeScript 從函數(shù)聲明中隱含了它的類型。
如果我們調(diào)用函數(shù)的值的類型與函數(shù)預期的類型不同,TypeScript 編譯器 (tsc) 會給我們錯誤 2345。對 sum 函數(shù)執(zhí)行以下調(diào)用:
sum('shark', 'whale');這將給出以下內(nèi)容:
OutputArgument of type 'string' is not assignable to parameter of type 'number'. (2345)
我們可以在函數(shù)中使用任何類型,而不僅僅是基本類型。例如,假設我們有一個看起來像這樣的 User 類型:
type User = {firstName: string;lastName: string;};
我們可以創(chuàng)建一個返回用戶全名的函數(shù),如下所示:
function getUserFullName(user: User): string {return `${user.firstName} ${user.lastName}`;}
大多數(shù)時候 TypeScript 足夠聰明,可以推斷出函數(shù)的返回類型,因此,在這種情況下,我們可以從函數(shù)聲明中刪除返回類型:
function getUserFullName(user: User) {return `${user.firstName} ${user.lastName}`;}
請注意,我們刪除了 : string 部分,它是函數(shù)的返回類型。當我們在函數(shù)體中返回字符串時,TypeScript 正確地假定我們的函數(shù)具有字符串返回類型。
要現(xiàn)在調(diào)用我們的函數(shù),我們必須傳遞一個與 User 類型具有相同形狀的對象:
type User = {firstName: string;lastName: string;};function getUserFullName(user: User) {return `${user.firstName} ${user.lastName}`;}const user: User = {firstName: "Jon",lastName: "Doe"};const userFullName = getUserFullName(user);
此代碼將成功通過 TypeScript 類型檢查器。如果我們將鼠標懸停在編輯器中的 userFullName 常量上,編輯器會將其類型識別為字符串。
TypeScript 中的可選函數(shù)參數(shù)
創(chuàng)建函數(shù)時并不總是需要所有參數(shù)。在本節(jié)中,我們將學習如何在 TypeScript 中將函數(shù)參數(shù)標記為可選。
要將函數(shù)參數(shù)轉換為可選參數(shù),請?zhí)砑?? 參數(shù)名稱后面的修飾符。給定一個類型為 T 的函數(shù)參數(shù) param1,我們可以通過添加 ? 使 param1 成為可選參數(shù),如下所示:
param1?: T例如,為我們的 getUserFullName 函數(shù)添加一個可選的前綴參數(shù),它是一個可選字符串,可以作為前綴添加到用戶的全名:
type User = {firstName: string;lastName: string;};function getUserFullName(user: User, prefix?: string) {return `${prefix ?? ''}${user.firstName} ${user.lastName}`;}
在此代碼塊的第一個突出顯示部分中,我們正在向函數(shù)添加一個可選的前綴參數(shù),在第二個突出顯示部分中,我們將使用它作為用戶全名的前綴。為此,我們正在使用無效合并運算符 ??。這樣,我們將僅使用已定義的前綴值;否則,該函數(shù)將使用空字符串。
現(xiàn)在,我們可以使用或不使用前綴參數(shù)調(diào)用我們的函數(shù),如下所示:
type User = {firstName: string;lastName: string;};function getUserFullName(user: User, prefix?: string) {return `${prefix ?? ''} ${user.firstName} ${user.lastName}`;}const user: User = {firstName: "Jon",lastName: "Doe"};const userFullName = getUserFullName(user);const mrUserFullName = getUserFullName(user, 'Mr. ');
在這種情況下,userFullName 的值為 Jon Doe,而 mrUserFullName 的值為 Mr. Jon Doe。
請注意,我們不能在必需參數(shù)之前添加可選參數(shù);它必須在系列的最后列出,就像 (user: User, prefix?: string) 一樣。首先,列出它會使 TypeScript Compiler 返回錯誤 1016:
OutputA required parameter cannot follow an optional parameter. (1016)
鍵入的箭頭函數(shù)表達式
到目前為止,本教程已經(jīng)展示了如何在 TypeScript 中鍵入使用 function 關鍵字定義的普通函數(shù)。但在 JavaScript 中,我們可以通過多種方式定義函數(shù),例如使用箭頭函數(shù)。在本節(jié)中,我們將向 TypeScript 中的箭頭函數(shù)添加類型。
向箭頭函數(shù)添加類型的語法與向普通函數(shù)添加類型幾乎相同。為了說明這一點,請將?getUserFullName 函數(shù)更改為箭頭函數(shù)表達式:
const getUserFullName = (user: User, prefix?: string) => `${prefix ?? ''}${user.firstName} ${user.lastName}`;如果我們想明確說明函數(shù)的返回類型,可以在 () 之后添加它,如以下代碼塊中突出顯示的代碼所示:
const getUserFullName = (user: User, prefix?: string): string => `${prefix ?? ''}${user.firstName} ${user.lastName}`;現(xiàn)在,我們可以像以前一樣使用你的函數(shù)了:
type User = {firstName: string;lastName: string;};const getUserFullName = (user: User, prefix?: string) => `${prefix ?? ''}${user.firstName} ${user.lastName}`;const user: User = {firstName: "Jon",lastName: "Doe"};const userFullName = getUserFullName(user);
這將毫無錯誤地通過 TypeScript 類型檢查器。
注意:請記住,對 JavaScript 中的函數(shù)有效的所有內(nèi)容也對 TypeScript 中的函數(shù)有效。
函數(shù)類型
在前面的內(nèi)容中,我們向 TypeScript 中的函數(shù)的參數(shù)和返回值添加了類型。在本節(jié)中,我們將學習如何創(chuàng)建函數(shù)類型,它們是表示特定函數(shù)簽名的類型。在將函數(shù)傳遞給其他函數(shù)時,創(chuàng)建與特定函數(shù)匹配的類型特別有用,例如,具有本身就是函數(shù)的參數(shù)。這是創(chuàng)建接受回調(diào)的函數(shù)時的常見模式。
創(chuàng)建函數(shù)類型的語法類似于創(chuàng)建箭頭函數(shù),但有兩點不同:
我們刪除了函數(shù)體。
我們使函數(shù)聲明返回返回類型本身。
以下是創(chuàng)建與我們一直使用的 getUserFullName 函數(shù)匹配的類型的方法:
type User = {firstName: string;lastName: string;};type PrintUserNameFunction = (user: User, prefix?: string) => string;
在此示例中,我們使用 type 關鍵字聲明了一個新類型,然后,為括號中的兩個參數(shù)提供了類型,并為箭頭后面的返回值提供了類型。
舉一個更具體的例子,假設我們正在創(chuàng)建一個名為 onEvent 的事件偵聽器函數(shù),它接收事件名稱作為第一個參數(shù),第二個參數(shù)接收事件回調(diào)。事件回調(diào)本身將接收具有以下類型的對象作為第一個參數(shù):
type EventContext = {value: string;};
然后,我們可以像這樣編寫 onEvent 函數(shù):
type EventContext = {value: string;};function onEvent(eventName: string, eventCallback: (target: EventContext) => void) {// ... implementation}
注意 eventCallback 參數(shù)的類型是一個函數(shù)類型:
eventCallback: (target: EventTarget) => void這意味著我們的 onEvent 函數(shù)需要在 eventCallback 參數(shù)中傳遞另一個函數(shù)。此函數(shù)應接受 EventTarget 類型的單個參數(shù)。我們的 onEvent 函數(shù)會忽略此函數(shù)的返回類型,因此,我們使用 void 作為類型。
使用類型化異步函數(shù)
在使用 JavaScript 時,使用異步函數(shù)是比較常見的。TypeScript 有一種特定的方法來處理這個問題。在本節(jié)中,我們將在 TypeScript 中創(chuàng)建異步函數(shù)。
創(chuàng)建異步函數(shù)的語法與用于 JavaScript 的語法相同,但添加了允許類型:
async function asyncFunction(param1: number) {// ... function implementation ...}
向普通函數(shù)添加類型和向異步函數(shù)添加類型之間有一個主要區(qū)別:在異步函數(shù)中,返回類型必須始終是 Promise
假設我們有一個用戶類型:
type User = {id: number;firstName: string;};
還想象一下,我們在數(shù)據(jù)存儲中有一些用戶對象。這些數(shù)據(jù)可以存儲在任何地方,例如文件、數(shù)據(jù)庫或 API 請求后面。為簡單起見,在此示例中,我們將使用數(shù)組:
type User = {id: number;firstName: string;};const users: User[] = [{ id: 1, firstName: "Jane" },{ id: 2, firstName: "Jon" }];
如果我們想創(chuàng)建一個類型安全的函數(shù),以異步方式按 ID 檢索用戶,我們可以這樣做:
async function getUserById(userId: number): Promise{ const foundUser = users.find(user => user.id === userId);if (!foundUser) {return null;}return foundUser;}
在此函數(shù)中,我們首先將函數(shù)聲明為異步:
async function getUserById(userId: number): Promise { 然后,我們指定它接受作為第一個參數(shù)的用戶 ID,它必須是一個數(shù)字:
async function getUserById(userId: number): Promise { getUserById 的返回類型是一個 Promise,它解析為 User 或 null。我們正在使用聯(lián)合類型 User | null 作為 Promise 泛型的類型參數(shù)。
用戶 | null 是 Promise
async function getUserById(userId: number): Promise { 使用 await 調(diào)用我們的函數(shù)并將結果存儲在名為 user 的變量中:
type User = {id: number;firstName: string;};const users: User[] = [{ id: 1, firstName: "Jane" },{ id: 2, firstName: "Jon" }];async function getUserById(userId: number): Promise{ const foundUser = users.find(user => user.id === userId);if (!foundUser) {return null;}return foundUser;}async function runProgram() {const user = await getUserById(1);}
注意:我們正在使用一個名為 runProgram 的包裝函數(shù),因為,我們不能在文件的頂層使用 await。這樣做會導致 TypeScript 編譯器發(fā)出錯誤 1375:
輸出'await' 表達式僅在文件是模塊時才允許在文件的頂層使用,但該文件沒有導入或導出。考慮添加一個空的“export {}”以使該文件成為一個模塊。(1375)
如果我們在編輯器或 TypeScript Playground 中將鼠標懸停在 user 上,我們會發(fā)現(xiàn) user 的類型為 User | null,這正是我們的 getUserById 函數(shù)返回的承諾解析為的類型。
如果刪除 await 并直接調(diào)用該函數(shù),則返回 Promise 對象:
async function runProgram() {const userPromise = getUserById(1);}
如果,我們將鼠標懸停在 userPromise 上,我們會發(fā)現(xiàn)它的類型是 Promise
大多數(shù)時候,TypeScript 可以推斷異步函數(shù)的返回類型,就像它對非異步函數(shù)所做的那樣。
因此,您可以省略 getUserById 函數(shù)的返回類型,因為它仍然被正確推斷為具有類型 Promise
async function getUserById(userId: number) {const foundUser = users.find(user => user.id === userId);if (!foundUser) {return null;}return foundUser;}
為 Rest 參數(shù)添加類型
剩余參數(shù)是 JavaScript 中的一項功能,它允許函數(shù)以單個數(shù)組的形式接收許多參數(shù)。在本節(jié)中,我們將在 TypeScript 中使用剩余參數(shù)。
通過使用 rest 參數(shù)后跟結果數(shù)組的類型,完全可以以類型安全的方式使用 rest 參數(shù)。以下面的代碼為例,其中有一個名為 sum 的函數(shù),它接受可變數(shù)量的數(shù)字并返回它們的總和:
function sum(...args: number[]) {return args.reduce((accumulator, currentValue) => {return accumulator + currentValue;}, 0);}
該函數(shù)使用 .reduce Array 方法迭代數(shù)組并將元素相加。請注意此處突出顯示的其余參數(shù) args。類型被設置為一個數(shù)字數(shù)組:number[]。
調(diào)用我們的函數(shù)正常工作:
function sum(...args: number[]) {return args.reduce((accumulator, currentValue) => {return accumulator + currentValue;}, 0);}const sumResult = sum(2, 4, 6, 8);
如果我們使用數(shù)字以外的任何內(nèi)容調(diào)用我們的函數(shù),例如:
const sumResult = sum(2, "b", 6, 8);TypeScript 編譯器將發(fā)出錯誤 2345:
OutputArgument of type 'string' is not assignable to parameter of type 'number'. (2345)
使用函數(shù)重載
程序員有時需要一個函數(shù)來接受不同的參數(shù),具體取決于函數(shù)的調(diào)用方式。在 JavaScript 中,這通常是通過有一個參數(shù)來完成的,該參數(shù)可以采用不同類型的值,如字符串或數(shù)字。將多個實現(xiàn)設置為相同的函數(shù)名稱稱為函數(shù)重載。
使用 TypeScript,我們可以創(chuàng)建函數(shù)重載,明確描述它們處理的不同情況,通過分別記錄重載函數(shù)的每個實現(xiàn)來改善開發(fā)人員體驗。
本節(jié)將介紹如何在 TypeScript 中使用函數(shù)重載。
假設我們有一個用戶類型:
type User = {id: number;email: string;fullName: string;age: number;};
并且我們想創(chuàng)建一個可以使用以下任何信息查找用戶的函數(shù):
ID
電子郵件
年齡和全名
我們可以像這樣創(chuàng)建這樣的函數(shù):
function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {// ... code}
該函數(shù)使用 | 運算符為 idOrEmailOrAge 和返回值組成類型的聯(lián)合。
接下來,為我們希望使用函數(shù)的每種方式添加函數(shù)重載,如以下突出顯示的代碼所示:
type User = {id: number;email: string;fullName: string;age: number;};function getUser(id: number): User | undefined;function getUser(email: string): User | undefined;function getUser(age: number, fullName: string): User | undefined;function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {// ... code}
此函數(shù)具有三個重載,每個重載一個用于檢索用戶。創(chuàng)建函數(shù)重載時,在函數(shù)實現(xiàn)本身之前添加函數(shù)重載。函數(shù)重載沒有主體;他們只有參數(shù)列表和返回類型。
接下來,實現(xiàn)函數(shù)本身,它應該有一個與所有函數(shù)重載兼容的參數(shù)列表。在前面的示例中,我們的第一個參數(shù)可以是數(shù)字或字符串,因為它可以是 id、電子郵件或年齡:
function getUser(id: number): User | undefined;function getUser(email: string): User | undefined;function getUser(age: number, fullName: string): User | undefined;function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {// ... code}
因此,我們在函數(shù)實現(xiàn)中將 idOrEmailorAge 參數(shù)的類型設置為 number | string。這樣,它就與 getUser 函數(shù)的所有重載兼容。
我們還為函數(shù)添加了一個可選參數(shù),用于當用戶傳遞全名時:
function getUser(id: number): User | undefined;function getUser(email: string): User | undefined;function getUser(age: number, fullName: string): User | undefined;function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {// ... code}
實現(xiàn)的功能可能如下所示,其中,我們使用用戶數(shù)組作為用戶的數(shù)據(jù)存儲:
type User = {id: number;email: string;fullName: string;age: number;};const users: User[] = [{ id: 1, email: "[email protected]", fullName: "Jane Doe" , age: 35 },{ id: 2, email: "[email protected]", fullName: "Jon Doe", age: 35 }];function getUser(id: number): User | undefined;function getUser(email: string): User | undefined;function getUser(age: number, fullName: string): User | undefined;function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {if (typeof idOrEmailOrAge === "string") {return users.find(user => user.email === idOrEmailOrAge);}if (typeof fullName === "string") {return users.find(user => user.age === idOrEmailOrAge && user.fullName === fullName);} else {return users.find(user => user.id === idOrEmailOrAge);}}const userById = getUser(1);const userByEmail = getUser("[email protected]");const userByAgeAndFullName = getUser(35, "Jon Doe");
在此代碼中,如果 idOrEmailOrAge 是一個字符串,那么,我們可以使用電子郵件鍵搜索用戶。以下條件假設 idOrEmailOrAge 是一個數(shù)字,因此,它是 id 或年齡,具體取決于是否定義了 fullName。
函數(shù)重載的一個有趣的方面是,在大多數(shù)編輯器中,包括 VS Code 和 TypeScript Playground,只要我們鍵入函數(shù)名稱并打開第一個括號來調(diào)用函數(shù),就會出現(xiàn)一個彈出窗口,其中包含所有可用的重載, 如下圖所示:

如果我們?yōu)槊總€函數(shù)重載添加注釋,該注釋也將作為文檔來源出現(xiàn)在彈出窗口中。例如,將以下突出顯示的注釋添加到示例重載中:
.../*** Get a user by their ID.*/function getUser(id: number): User | undefined;/*** Get a user by their email.*/function getUser(email: string): User | undefined;/*** Get a user by their age and full name.*/function getUser(age: number, fullName: string): User | undefined;...
現(xiàn)在,當我們將鼠標懸停在這些函數(shù)上時,將為每個重載顯示注釋,如下面的動畫所示:

用戶定義的類型保護
本教程將檢查 TypeScript 中函數(shù)的最后一個特性是用戶定義的類型保護,它們是允許 TypeScript 更好地推斷某些值的類型的特殊函數(shù)。這些守衛(wèi)在條件代碼塊中強制執(zhí)行某些類型,其中值的類型可能會根據(jù)情況而有所不同。這些在使用 Array.prototype.filter 函數(shù)返回過濾的數(shù)據(jù)數(shù)組時特別有用。
有條件地向數(shù)組添加值時的一項常見任務是檢查某些條件,然后,僅在條件為真時才添加值。如果該值不為真,則代碼向數(shù)組添加一個假布爾值。在使用該數(shù)組之前,我們可以使用 .filter(Boolean) 對其進行過濾,以確保僅返回真實值。
當使用值調(diào)用時,布爾構造函數(shù)返回 true 或 false,具體取決于此值是 Truthy 還是 Falsy 值。
例如,假設我們有一個字符串數(shù)組,并且如果其他標志為真,我們只想將字符串產(chǎn)生式包含到該數(shù)組中:
const isProduction = falseconst valuesArray = ['some-string', isProduction && 'production']function processArray(array: string[]) {// do something with array}processArray(valuesArray.filter(Boolean))
雖然,這是在運行時完全有效的代碼,但 TypeScript 編譯器會在編譯期間為我們提供錯誤 2345:
OutputArgument of type '(string | boolean)[]' is not assignable to parameter of type 'string[]'.Type 'string | boolean' is not assignable to type 'string'.Type 'boolean' is not assignable to type 'string'. (2345)
此錯誤表示,在編譯時,傳遞給 processArray 的值被解釋為 false | 的數(shù)組。字符串值,這不是 processArray 所期望的。它需要一個字符串數(shù)組:string[]。
這是 TypeScript 不夠聰明的一種情況,無法通過使用 .filter(Boolean) 來推斷我們正在從數(shù)組中刪除所有虛假值。但是,有一種方法可以向 TypeScript 提供這個提示:使用用戶定義的類型保護。
創(chuàng)建一個名為 isString 的用戶定義類型保護函數(shù):
function isString(value: any): value is string {return typeof value === "string"}
注意 isString 函數(shù)的返回類型。創(chuàng)建用戶定義類型保護的方法是使用以下語法作為函數(shù)的返回類型:
parameterName is Type其中 parameterName 是我們正在測試的參數(shù)的名稱,Type 是此函數(shù)返回 true 時此參數(shù)值的預期類型。
在這種情況下,如果 isString 返回 true,則表示 value 是一個字符串。我們還將 value 參數(shù)的類型設置為 any,因此,它適用于任何類型的值。
現(xiàn)在,更改?.filter 調(diào)用以使用的新函數(shù),而不是將其傳遞給布爾構造函數(shù):
const isProduction = falseconst valuesArray = ['some-string', isProduction && 'production']function processArray(array: string[]) {// do something with array}function isString(value: any): value is string {return typeof value === "string"}processArray(valuesArray.filter(isString))
現(xiàn)在 TypeScript 編譯器正確地推斷出傳遞給 processArray 的數(shù)組只包含字符串,并且,我們的代碼可以正確編譯。
結論
函數(shù)是 TypeScript 中應用程序的構建塊,在本教程中,我們學習了如何在 TypeScript 中構建類型安全的函數(shù),以及如何利用函數(shù)重載來更好地記錄單個函數(shù)的所有變體。擁有這些知識將允許在整個代碼中使用更多類型安全且易于維護的功能。

往期推薦



最后
歡迎加我微信,拉你進技術群,長期交流學習...
歡迎關注「前端Q」,認真學前端,做個專業(yè)的技術人...


