函式參數
以下函式:
function sing(song){
console.log(`Singing: ${song}!`);
}
song 參數因為沒有明確宣告型別資訊,所以 TypeScript 會認為它是 any 型別:表示參數的型別可以是任何型別。
若要告知 song 為 string 型別的話,可用以下寫法:
function sing(song: string){
console.log(`Singing: ${song}!`);
}
必要參數
TypeScript 在預設上,是假定所有參數都是必要的。如果使用錯誤數量的參數呼叫函式,TypeScript 將以型別錯誤的形式提出訊息。
例:
function singTwo(first: string, second: string) {
console.log(`${first} / ${second}`);
}
// 錯誤:應有 2 個引數,但只得到 1 個。
singTwo("Hi");
// 正確
singTwo("Hi", "Hello");
// 錯誤:應有 2 個引數,但卻得到 3 個。
singTwo("Hi", "Hello", "Wow");
註:參數(Parameter) 是指函式宣告所期望接收的參數。引數(Argument)是指函式在呼叫時,提供給參數的資料,例如上面程式碼中,first 和 second 是參數,而 “Hi”、”Hello” 之類的是引數。
選項參數
在 JavaScript 中,如果未提供函式參數,則函式內部的參數值,預設就是 undefined。
然而,在 TypeScript 中,可透過加上問號,來讓參數變成是可選擇的。
例如以下程式,singer 參數被標記為可選擇的,它的型別是 string | undefined。
function announceSong(song: string, singer?: string){
console.log(`Song: ${song}`);
if(singer){ // 將 singer 窄化為 string
console.log(`Singer: ${singer}`);
}
}
announceSong("Hi"); // 允許
announceSong("Hi", undefined); // 允許
announceSong("Hi", "Sia"); // 允許
如果是改成用以下的寫法,那麼 singer 參數就是一定要提供:
function announceSongBy(song: string, singer: string | undefined){
console.log(`Song: ${song}`);
if(singer){ // 將 singer 窄化為 string
console.log(`Singer: ${singer}`);
}
}
announceSongBy("Hi"); // 錯誤:應有2個引數,但只有1個。
announceSongBy("Hi", undefined); // 允許
announceSongBy("Hi", "Sia"); // 允許
註:函式當中的選擇性參數,必須擺在最後一個位置。倘若放在必要參數之前,那就會報錯。例:
// 以下參數會報錯,必要參數不得放在選擇性參數之後。
function announceSinger(singer?: string, song: string){
}
參數的預設值
可以使用等號來指派參數的預設值。
函式參數的預設值,若沒有給型別的話,TypeScript 將根據預設值推斷參數的型別:
// rating 參數的型別會推斷為 number | undefined
function rateSong(song: string, rating = 0){
console.log(`${song} 獲得了 ${rating} 顆星!`);
}
rateSong("Photograph"); // 允許,會印出「Photograph 獲得了 0 顆星!」
rateSong("hi1", 5); // 允許,會印出「hi1 獲得了 5 顆星!」
rateSong("hi2", undefined); // 允許,會印出「hi2 獲得了 0 顆星!」
rateSong("hi3", "100"); // 報錯,型別 string 的引數不可指派給型別 number 的參數。
剩餘參數
TypeScript 允許宣告剩餘(Rest)的型別,需要在結尾處增加 [] 語法,用以表示它是陣列的參數。
下例中,songs 為函式 singAllTheSongs 剩餘參數,並且是 0 個或多個 string 型別的陣列參數:
function singAllTheSongs(singer: string, ...songs: string[]){
for(const song of songs){
console.log(`${singer} 唱 ${song}。`);
}
}
singAllTheSongs("Alice"); // 允許
singAllTheSongs("Mary", "a", "b"); // 允許
singAllTheSongs("Mary", 100); // 報錯,型別 number 的引數不可指派給型別 string 的參數。
回傳型別
TypeScript 會自動依據函式的回傳值,來自動推斷該函式的回傳型別,
例 1:
// 型別:(songs: string[]) => number
function singSongs(songs: string[]){
for(const song of songs){
console.log(`${song}`);
}
return songs.length;
}
例 2:
// 型別:(songs: string[], index: number) => string | undefined
function getSongAt(songs: string[], index: number){
return index < songs.length ? songs[index] : undefined;
}
明確的回傳型別
與變數一樣,通常不建議以明確宣告方式,在函式的回傳型別中使用型別註記。
一般函式(下方的藍色文字即明確宣告該函式的回傳型別):
function abc(): number{
return 1;
}
如果是箭頭函式(下方的藍色文字即明確宣告該函式的回傳型別):
const abc = (): number => {
return 1;
};
函式型別
函式型別語法看起來類似於箭頭函式,但操控的是型別而非主體。
例1:
// 描述一個沒有參數,並且回傳 string 數值的函式
let nothingInGiveString: () => string;
例2:
// 描述一個帶有 string[] 參數、一個可選用的 count 參數,和一個回傳 number 的函式
let inputAndOutput: (songs: string[], count?: number) => number;
另外,函式型別也常用於描述回呼(callback)參數,例3:
const songs = ["a", "b", "c"];
function runOnSongs(getSongAt: (index: number) => string) {
for(let i = 0; i < songs.length; i++){
console.log(getSongAt(i));
}
}
function getSongAt(index: number){
return "" + songs[index] + "";
}
runOnSongs(getSongAt); // 允許
function logSong(song: string){
return "" + song + "";
}
runOnSongs(logSong); // 報錯:型別 (song: string) => string 引數,不可指派給型別 (index: number) => string 參數。參數 song 和 index 的型別不相容,型別 string 不可指派給型別 number。
函式型別使用小括號
在聯集型別中,小括號可用於標示哪一部份,是函式回傳的聯集型別:
// 一個回傳型別為 string | undefined 聯集的函式
let returnsStringOrUndefined: () => string | undefined;
// 可能為 undefined 或回傳型別為 string 的函式
let maybeReturnsString: (() => string) | undefined;
參數型別推斷
TypeScript 可以推斷函式中的參數型別,以及提供給相關位置的宣告型別。
例如以下:
let ginger: (song: string) => string;
// 以下的 song 型別推斷為 string
singer = function(song){
return "Singing: " + song.toUpperCase();
}
再例如以下:
const songs = ["a", "b", "c"];
// song 推斷為 string
// index 推斷為 number
songs.forEach((song, index) => {
console.log(song + " is at index " + index);
});
函式型別別名
例:
// 這個 StringToNumber 是一個函式型別別名
type StringToNumber = (input: string) => number;
let stringToNumber: StringToNumber;
stringToNumber = (input) => input.length; // 正確
let stringToNumber2: StringToNumber;
stringToNumber2 = (input) => input.toUpperCase(); // 會報錯:型別 string 不可指派給型別 number
函式型別別名也可以用在函式的參數。例:
// 這個 NumberToString 是一個函式型別別名
type NumberToString = (input: number) => string;
function usesNumberToString(numberToString: NumberToString) {
console.log("The string is: " + numberToString(1234));
}
usesNumberToString((input) => {
return input + "! Hooray!";
}); // 正確
// 以下錯誤:型別 number 不可指派給型別 string
usesNumberToString((input) => input * 2);
其它回傳型別
回傳 void 型別
有些函式,並沒有任何回傳值。可能是沒有任何 return 語句,或者也有可能是 return 語句上沒有回傳資料。TypeScript 允許使用 void 關鍵字,來表達這種情況。
回傳型別為 void 的函式,可能不會回傳值。因此下例的 logSong 函式,宣告為回傳 void,因此不允許回傳資料:
function logSong(song: string | undefined): void {
if(!song){
return; // 正確
}
console.log("" + song + "");
return true; // 錯誤:型別 boolean 不可指派給型別 void
}
void 可用在函式型別宣告中的回傳型別。在函式型別宣告中使用時,void 表示函式的任何回傳值都會被忽略。例如以下範例:
// songLogger 變數會是一個函式,它接收一個字串,且不回傳資料
let songLogger: (song: string) => void;
songLogger = (song) => {
console.log("" + song + "");
};
songLogger("Hello"); // 正確
注意:雖然 JavaScript 函式在沒有實際回傳資料的情況下,預設是回傳 undefined ,但 void 與 undefined 是不一樣的。void 表示函式的回傳型別將被忽略,而 undefined 是回傳的字面數值。嘗試將型別為 void 的值指派給型別包含 undefined 的值,會造成型別錯誤:
// returnsVoid 的回傳型別會推斷為 void
function returnsVoid(){
return;
}
let lazyValue: string | undefined;
// 以下錯誤:型別 void 不可指派給型別 string | undefined
lazyValue = returnsVoid();
最後留意:void 型別在 JavaScript 中並沒有,它是 TypeScript 中的關鍵字,用於宣告函式的回傳型別。需記得,void 並不是表示函式本身可以回傳的數值,而是回傳值並不打算使用。
回傳 never 型別
有些函式不僅不回傳資料,反而是刻意不回傳。永遠不回傳的函式,是那些總是拋出錯誤或執行無限循環的函式。
如果函式刻意永遠不回傳資料,那就需明確加入 never 型別註記,表示執行該函式後的任何程式碼都不會執行。
例:
function fail(message: string): never {
throw new Error("錯誤:" + message);
}
function workWithUnsafeParam(param: unknown) {
if(typeof param !== "string"){
fail("參數應是 string。");
}
// 這裡的 param 已知是 string 型別
param.toUpperCase(); // 正確
}
註:never 不等於 void。void 用於不回傳任何資料的函式;never 用於表示永遠不會回傳的函式。
函式重載
某些 JavaScript 函式可以使用完全不同的參數集合來呼叫,這些參數集合會以可選擇的參數或剩餘參數來表示。
這些函式在 TypeScript 語法,可稱為 重載特徵/重載簽章(overload signatures) 來描述:在一個最終實做特徵(implementation signature)的函式主體之前,先宣告多個不同版本的函式名稱、參數和回傳型別。
下例:前兩行是重載特徵,第三行是實作特徵:
function createDate(timestamp: number): Date;
function createDate(month: number, day: number, year: number): Date;
function createDate(monthOrTimestamp: number, day?: number, year?: number) {
return day === undefined || year === undefined ? new Date(monthOrTimestamp) : new Date(year, monthOrTimestamp, day);
}
createDate(554356800); // 正確
createDate(7, 28, 1987); // 正確
// 報錯:沒有任何重載預期 2 個引數,但有重載預期1或3個引數
createDate(4, 1);
註:函式重載通常用作複雜,用來表示難以敘述的函式型別之最後手段。一般來說,最好還是保持函式的簡單,並且盡可能避免使用函式重載。
發佈留言
很抱歉,必須登入網站才能發佈留言。