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

如果我們?yōu)槊總€(gè)函數(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)在,當(dāng)我們將鼠標(biāo)懸停在這些函數(shù)上時(shí),將為每個(gè)重載顯示注釋,如下面的動(dòng)畫所示:

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

往期推薦



最后
歡迎加我微信,拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...
歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個(gè)專業(yè)的技術(shù)人...


