跳至主要内容

[TS] TypeScript Function Type

定義函式的型別

當一個函式被建立但沒有對其參數進行型別註記時,預設會被套用 any這是不建議的寫法

使用 Type Alias 定義函式型別

type Greet = (name: string, age: number) => string;

const greet: Greet = (name, age) => {
return `Hello ${name}, your age is ${age}.`;
};

使用 interface 定義函式的型別

interface Greet {
(name: string, age: number): string;
}

const greet: Greet = (name, age) => {
return `Hello ${name}, your age is ${age}.`;
};

在函式中定義參數的型別

使用箭頭函式

/**
* 範例一
**/

const add = (a: number, b: number): number => a + b;

// 等同於,在變數後直接註記型別為函式
// (a: number, b: number) => number 是 type
const add: (a: number, b: number) => number = (a, b) => a + b;

/**
* 範例二
**/

interface FnParams {
name: string;
phone: string;
}
interface FnResponse {
recipient: string;
body: string;
}

const sendTextMessage = (to: FnParams): FnResponse => ({
recipient: `${to.name} <${to.phone}>`, // Aaron <0912345678>
body: "You're pre-qualified for a loan",
});

使用 function 語法

使用 function 關鍵字來建立函式:

/**
* 範例一
**/

// function statement
function add(x: number, y: number): number {
return x + y;
}

// function expression
const add = function (a: number, b: number): number {
return a + b;
};

/**
* 範例二
**/

interface FnParams {
name: string;
email: string;
}
interface FnResponse {
recipient: string;
body: string;
}

// function statement
function sendEmail(to: FnParams): FnResponse {
return {
recipient: `${to.name} <${to.email}>`, // Aaron <pjchender@gmail.com>
body: "You're pre-qualified for a loan",
};
}

// function expression
const sendEmail = function (to: FnParams): FnResponse {
return {
recipient: `${to.name} <${to.email}>`, // Aaron <pjchender@gmail.com>
body: "You're pre-qualified for a loan",
};
};

參數中解構賦值

const greet = ({ firstName, lastName }: { firstName: string; lastName: string }): void => {
console.log(`Hello ${lastName} ${firstName}`);
};

選填性的參數與參數預設值

Optional and Default Parameters @ TypeScript Docs

選填性參數(optional parameters)

雖然在 JavaScript 中沒有被帶入的函式參數會是 undefined 且可以正常運作,但在 TypeScript 中,函式的參數預設都是必填的,沒填的話 compiler 會直接噴錯。

若有函式的參數需要是選填的,可以在參數後面使用 ?,例如:

// firstName 是必填,lastName 則可以選填
const getName = (firstName: string, lastName?: string) =>
lastName ? `${firstName} ${lastName}` : firstName;
warning

optional parameter 一定只能放在 required parameter 的後面。

使用參數預設值(default-initialized parameters)

除了函式的參數可以選填之外,同樣可以使用參數的預設值:

const getName = (firstName: string, lastName = 'Chen') => `${firstName} ${lastName}`;
getName('Aaron');

此時 lastName 的 type 一樣會是 lastName?: string,因此當函式的參數 lastName 沒有給值時,會自動使用 Chen

然而,和一般的 optional parameters 不同,default-initialized parameters 不一定只能放在 required parameters 的後面,但如果放在前面的話,在呼叫該函式的時候要使用 undefined。例如:

const getName = (firstName = 'Aaron', lastName: string) => `${firstName} ${lastName}`;

// 當 default parameters 在 required parameters 前,使用時需要帶入 undefined
getName(undefined, 'Chen');

使用 type/interface 定義參數型別,並帶入參數預設值

TypeScript Default Parameters @ typescript tutorial

先定義參數的型別後,再來定義參數的預設值:

interface GetNameArgs {
firstName?: string;
lastName?: string;
}

const getName = ({ firstName, lastName }: GetNameArgs = {}) => {
return `${firstName} ${lastName}`;
};

// 等同於
function getName({ firstName, lastName }: GetNameArgs = {}) {
return `${firstName} ${lastName}`;
}

Rest Parameters

Rest Parameters @ TypeScript Docs

在 TypeScript 中一樣可以使用 rest parameters:

// example 1
const getName = (firstName: string, ...rest: string[]) => `${firstName} ${rest.join(' ')}`;
const name = getName('PJ', 'Aaron', 'Chen');

// example 2
const sum = (...values: number[]) =>
values.reduce((accumulate, current) => accumulate + current, 0);
const total = sum(1, 2, 3, 4);

Function Overload(函式超載)

函式超載指的是擴充一個函式可以被執行的形式(例如,介面)。簡單來說就是針對同一個函式提供多個不同的 type definition

Function Overload 會包含兩個部分:

  • overload signatures:也就是 type definition 的部分
  • function implementation:實際上執行的 function,它的型別需要滿足所有的 overload signatures
/**
* Provide multiple function signatures (overload signatures)
*/
// "overload signatures"
function contactPeople(method: 'email', ...people: HasEmail[]): void;
function contactPeople(method: 'phone', ...people: HasPhoneNumber[]): void;

// "function implementation": 需要同時符合 overload signatures 的情況
// 期望 method 是 emails 時,...people 的型別是 HasEmail[]
// 而 method 是 phone 時,...people 的型別是 HasPhoneNumber[]
// 這時可以在函式的上方撰寫 overload signatures
function contactPeople(method: 'email' | 'phone', ...people: (HasEmail | HasPhoneNumber)[]) {
if (method === 'email') {
(people as HasEmail[]).forEach(sendEmail);
} else if (method === 'phone') {
(people as HasPhoneNumber[]).forEach(sendTextMessage);
}
}

contactPeople('email', { name: 'Aaron', email: 'pjchender@gmail.com' });

// 如果沒有使用 overload signature 的話,下面這行的用法 TS 不會報錯,但與我們的預期不同
// 因為我們希望 method 是 'phone' 時,後面物件的型別要是 HasPhoneNumber
contactPeople('phone', { name: 'Aaron', email: 'pjchender@gmail.com' }); // 在使用了 function overload 之後,TS 會報錯誤

成功使用 function overload 之後,VSCode 的 parameter hints 會跳出提示來說明可用的 function signatures:

function overload