TypeScript 之 More on Functions

冴羽發表於2021-11-19

前言

TypeScript 的官方文件早已更新,但我能找到的中文文件都還停留在比較老的版本。所以對其中新增以及修訂較多的一些章節進行了翻譯整理。

本篇整理自 TypeScript Handbook 中 「More on Functions」 章節。

本文並不嚴格按照原文翻譯,對部分內容也做了解釋補充。

正文

函式是任何應用的基礎組成部分,無論它是區域性函式(local functions),還是從其他模組匯入的函式,亦或是類中的方法。當然,函式也是值 (values),而且像其他值一樣,TypeScript 有很多種方式用來描述,函式可以以怎樣的方式被呼叫。讓我們來學習一下如何書寫描述函式的型別(types)。

函式型別表示式(Function Type Expressions)

最簡單描述一個函式的方式是使用函式型別表示式(function type expression)。它的寫法有點類似於箭頭函式:

function greeter(fn: (a: string) => void) {
  fn("Hello, World");
}
 
function printToConsole(s: string) {
  console.log(s);
}
 
greeter(printToConsole);

語法 (a: string) => void 表示一個函式有一個名為 a ,型別是字串的引數,這個函式並沒有返回任何值。

如果一個函式引數的型別並沒有明確給出,它會被隱式設定為 any

注意函式引數的名字是必須的,這種函式型別描述 (string) => void,表示的其實是一個函式有一個型別是 any,名為 string 的引數。

當然了,我們也可以使用型別別名(type alias)定義一個函式型別:

type GreetFunction = (a: string) => void;
function greeter(fn: GreetFunction) {
  // ...
}

呼叫簽名(Call Signatures)

在 JavaScript 中,函式除了可以被呼叫,自己也是可以有屬性值的。然而上一節講到的函式型別表示式並不能支援宣告屬性,如果我們想描述一個帶有屬性的函式,我們可以在一個物件型別中寫一個呼叫簽名(call signature)。

type DescribableFunction = {
  description: string;
  (someArg: number): boolean;
};
function doSomething(fn: DescribableFunction) {
  console.log(fn.description + " returned " + fn(6));
}

注意這個語法跟函式型別表示式稍有不同,在引數列表和返回的型別之間用的是 : 而不是 =>

構造簽名 (Construct Signatures)

JavaScript 函式也可以使用 new 操作符呼叫,當被呼叫的時候,TypeScript 會認為這是一個建構函式(constructors),因為他們會產生一個新物件。你可以寫一個構造簽名,方法是在呼叫簽名前面加一個 new 關鍵詞:

type SomeConstructor = {
  new (s: string): SomeObject;
};
function fn(ctor: SomeConstructor) {
  return new ctor("hello");
}

一些物件,比如 Date 物件,可以直接呼叫,也可以使用 new 操作符呼叫,而你可以將呼叫簽名和構造簽名合併在一起:

interface CallOrConstruct {
  new (s: string): Date;
  (n?: number): number;
}

泛型函式 (Generic Functions)

我們經常需要寫這種函式,即函式的輸出型別依賴函式的輸入型別,或者兩個輸入的型別以某種形式相互關聯。讓我們考慮這樣一個函式,它返回陣列的第一個元素:

function firstElement(arr: any[]) {
  return arr[0];
}

注意此時函式返回值的型別是 any,如果能返回第一個元素的具體型別就更好了。

在 TypeScript 中,泛型就是被用來描述兩個值之間的對應關係。我們需要在函式簽名裡宣告一個型別引數 (type parameter)

function firstElement<Type>(arr: Type[]): Type | undefined {
  return arr[0];
}

通過給函式新增一個型別引數 Type,並且在兩個地方使用它,我們就在函式的輸入(即陣列)和函式的輸出(即返回值)之間建立了一個關聯。現在當我們呼叫它,一個更具體的型別就會被判斷出來:

// s is of type 'string'
const s = firstElement(["a", "b", "c"]);
// n is of type 'number'
const n = firstElement([1, 2, 3]);
// u is of type undefined
const u = firstElement([]);

推斷(Inference)

注意在上面的例子中,我們沒有明確指定 Type 的型別,型別是被 TypeScript 自動推斷出來的。

我們也可以使用多個型別引數,舉個例子:

function map<Input, Output>(arr: Input[], func: (arg: Input) => Output): Output[] {
  return arr.map(func);
}
 
// Parameter 'n' is of type 'string'
// 'parsed' is of type 'number[]'
const parsed = map(["1", "2", "3"], (n) => parseInt(n));

注意在這個例子中,TypeScript 既可以推斷出 Input 的型別 (從傳入的 string 陣列),又可以根據函式表示式的返回值推斷出 Output 的型別。

約束(Constraints)

有的時候,我們想關聯兩個值,但只能操作值的一些固定欄位,這種情況,我們可以使用約束(constraint)對型別引數進行限制。

讓我們寫一個函式,函式返回兩個值中更長的那個。為此,我們需要保證傳入的值有一個 number 型別的 length 屬性。我們使用 extends 語法來約束函式引數:

function longest<Type extends { length: number }>(a: Type, b: Type) {
  if (a.length >= b.length) {
    return a;
  } else {
    return b;
  }
}
 
// longerArray is of type 'number[]'
const longerArray = longest([1, 2], [1, 2, 3]);
// longerString is of type 'alice' | 'bob'
const longerString = longest("alice", "bob");
// Error! Numbers don't have a 'length' property
const notOK = longest(10, 100);
// Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.

TypeScript 會推斷 longest 的返回型別,所以返回值的型別推斷在泛型函式裡也是適用的。

正是因為我們對 Type 做了 { length: number } 限制,我們才可以被允許獲取 a b引數的 .length 屬性。沒有這個型別約束,我們甚至不能獲取這些屬性,因為這些值也許是其他型別,並沒有 length 屬性。

基於傳入的引數,longerArraylongerString 中的型別都被推斷出來了。記住,所謂泛型就是用一個相同型別來關聯兩個或者更多的值。

泛型約束實戰(Working with Constrained Values)

這是一個使用泛型約束常出現的錯誤:

function minimumLength<Type extends { length: number }>(
  obj: Type,
  minimum: number
): Type {
  if (obj.length >= minimum) {
    return obj;
  } else {
    return { length: minimum };
    // Type '{ length: number; }' is not assignable to type 'Type'.
    // '{ length: number; }' is assignable to the constraint of type 'Type', but 'Type' could be instantiated with a different subtype of constraint '{ length: number; }'.
  }
}

這個函式看起來像是沒有問題,Type{ length: number} 約束,函式返回 Type 或者一個符合約束的值。

而這其中的問題就在於函式理應返回與傳入引數相同型別的物件,而不僅僅是符合約束的物件。我們可以寫出這樣一段反例:

// 'arr' gets value { length: 6 }
const arr = minimumLength([1, 2, 3], 6);
// and crashes here because arrays have
// a 'slice' method, but not the returned object!
console.log(arr.slice(0));

宣告型別引數 (Specifying Type Arguments)

TypeScript 通常能自動推斷泛型呼叫中傳入的型別引數,但也並不能總是推斷出。舉個例子,有這樣一個合併兩個陣列的函式:

function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
  return arr1.concat(arr2);
}

如果你像下面這樣呼叫函式就會出現錯誤:

const arr = combine([1, 2, 3], ["hello"]);
// Type 'string' is not assignable to type 'number'.

而如果你執意要這樣做,你可以手動指定 Type

const arr = combine<string | number>([1, 2, 3], ["hello"]);

寫一個好的泛型函式的一些建議

儘管寫泛型函式很有意思,但也容易翻車。如果你使用了太多的型別引數,或者使用了一些並不需要的約束,都可能會導致不正確的型別推斷。

型別引數下移(Push Type Parameters Down)

下面這兩個函式的寫法很相似:

function firstElement1<Type>(arr: Type[]) {
  return arr[0];
}
 
function firstElement2<Type extends any[]>(arr: Type) {
  return arr[0];
}
 
// a: number (good)
const a = firstElement1([1, 2, 3]);
// b: any (bad)
const b = firstElement2([1, 2, 3]);

第一眼看上去,兩個函式可太相似了,但是第一個函式的寫法可比第二個函式好太多了。第一個函式可以推斷出返回的型別是 number,但第二個函式推斷的返回型別卻是 any,因為 TypeScript 不得不用約束的型別來推斷 arr[0] 表示式,而不是等到函式呼叫的時候再去推斷這個元素。

關於本節原文中的 push down 含義,在《重構》裡,就有一個函式下移(Push Down Method)的優化方法,指如果超類中的某個函式只與一個或者少數幾個子類有關,那麼最好將其從超類中挪走,放到真正關心它的子類中去。即只在超類保留共用的行為。這種將超類中的函式本體複製到具體需要的子類的方法就可以稱之為 "push down",與本節中的去除 extend any[],將其具體的推斷交給 Type 自身就類似於 push down

Rule: 如果可能的話,直接使用型別引數而不是約束它

使用更少的型別引數 (Use Fewer Type Parameters)

這是另一對看起來很相似的函式:

function filter1<Type>(arr: Type[], func: (arg: Type) => boolean): Type[] {
  return arr.filter(func);
}
 
function filter2<Type, Func extends (arg: Type) => boolean>(
  arr: Type[],
  func: Func
): Type[] {
  return arr.filter(func);
}

我們建立了一個並沒有關聯兩個值的型別引數 Func,這是一個危險訊號,因為它意味著呼叫者不得不毫無理由的手動指定一個額外的型別引數。Func 什麼也沒做,卻導致函式更難閱讀和推斷。

Rule: 儘可能用更少的型別引數

型別引數應該出現兩次 (Type Parameters Should Appear Twice)

有的時候我們會忘記一個函式其實並不需要泛型

function greet<Str extends string>(s: Str) {
  console.log("Hello, " + s);
}
 
greet("world");

其實我們可以如此簡單的寫這個函式:

function greet(s: string) {
  console.log("Hello, " + s);
}

記住:型別引數是用來關聯多個值之間的型別。如果一個型別引數只在函式簽名裡出現了一次,那它就沒有跟任何東西產生關聯。

Rule: 如果一個型別引數僅僅出現在一個地方,強烈建議你重新考慮是否真的需要它

可選引數(Optional Parameters)

JavaScript 中的函式經常會被傳入非固定數量的引數,舉個例子:numbertoFixed 方法就支援傳入一個可選的引數:

function f(n: number) {
  console.log(n.toFixed()); // 0 arguments
  console.log(n.toFixed(3)); // 1 argument
}

我們可以使用 ? 表示這個引數是可選的:

function f(x?: number) {
  // ...
}
f(); // OK
f(10); // OK

儘管這個引數被宣告為 number型別,x 實際上的型別為 number | undefiend,這是因為在 JavaScript 中未指定的函式引數就會被賦值 undefined

你當然也可以提供有一個引數預設值:

function f(x = 10) {
  // ...
}

現在在 f 函式體內,x 的型別為 number,因為任何 undefined 引數都會被替換為 10。注意當一個引數是可選的,呼叫的時候還是可以傳入 undefined

declare function f(x?: number): void;
// cut
// All OK
f();
f(10);
f(undefined);

回撥中的可選引數(Optional Parameters in Callbacks)

在你學習過可選引數和函式型別表示式後,你很容易在包含了回撥函式的函式中,犯下面這種錯誤:

function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i], i);
  }
}

index?作為一個可選引數,本意是希望下面這些呼叫是合法的:

myForEach([1, 2, 3], (a) => console.log(a));
myForEach([1, 2, 3], (a, i) => console.log(a, i));

但 TypeScript 並不會這樣認為,TypeScript 認為想表達的是回撥函式可能只會被傳入一個引數,換句話說,myForEach 函式也可能是這樣的:

function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
  for (let i = 0; i < arr.length; i++) {
    // I don't feel like providing the index today
    callback(arr[i]);
  }
}

TypeScript 會按照這個意思理解並報錯,儘管實際上這個錯誤並無可能:

myForEach([1, 2, 3], (a, i) => {
  console.log(i.toFixed());
  // Object is possibly 'undefined'.
});

那如何修改呢?不設定為可選引數其實就可以:

function myForEach(arr: any[], callback: (arg: any, index: number) => void) {
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i], i);
  }
}

myForEach([1, 2, 3], (a, i) => {
  console.log(a);
});

在 JavaScript 中,如果你呼叫一個函式的時候,傳入了比需要更多的引數,額外的引數就會被忽略。TypeScript 也是同樣的做法。

當你寫一個回撥函式的型別時,不要寫一個可選引數, 除非你真的打算呼叫函式的時候不傳入實參

函式過載(Function Overloads)

一些 JavaScript 函式在呼叫的時候可以傳入不同數量和型別的引數。舉個例子。你可以寫一個函式,返回一個日期型別 Date,這個函式接收一個時間戳(一個引數)或者一個 月/日/年 的格式 (三個引數)。

在 TypeScript 中,我們可以通過寫過載簽名 (overlaod signatures) 說明一個函式的不同呼叫方法。 我們需要寫一些函式簽名 (通常兩個或者更多),然後再寫函式體的內容:

function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
  if (d !== undefined && y !== undefined) {
    return new Date(y, mOrTimestamp, d);
  } else {
    return new Date(mOrTimestamp);
  }
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
const d3 = makeDate(1, 3);

// No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.

在這個例子中,我們寫了兩個函式過載,一個接受一個引數,另外一個接受三個引數。前面兩個函式簽名被稱為過載簽名 (overload signatures)。

然後,我們寫了一個相容簽名的函式實現,我們稱之為實現簽名 (implementation signature) ,但這個簽名不能被直接呼叫。儘管我們在函式宣告中,在一個必須引數後,宣告瞭兩個可選引數,它依然不能被傳入兩個引數進行呼叫。

過載簽名和實現簽名(Overload Signatures and the Implementation Signature)

這是一個常見的困惑。大家常會這樣寫程式碼,但是又不理解為什麼會報錯:

function fn(x: string): void;
function fn() {
  // ...
}
// Expected to be able to call with zero arguments
fn();
Expected 1 arguments, but got 0.

再次強調一下,寫進函式體的簽名是對外部來說是“不可見”的,這也就意味著外界“看不到”它的簽名,自然不能按照實現簽名的方式來呼叫。

實現簽名對外界來說是不可見的。當寫一個過載函式的時候,你應該總是需要來兩個或者更多的簽名在實現簽名之上。

而且實現簽名必須和過載簽名必須相容(compatible),舉個例子,這些函式之所以報錯就是因為它們的實現簽名並沒有正確的和過載簽名匹配。

function fn(x: boolean): void;
// Argument type isn't right
function fn(x: string): void;
// This overload signature is not compatible with its implementation signature.
function fn(x: boolean) {}
function fn(x: string): string;
// Return type isn't right
function fn(x: number): boolean;
This overload signature is not compatible with its implementation signature.
function fn(x: string | number) {
  return "oops";
}

寫一個好的函式過載的一些建議

就像泛型一樣,也有一些建議提供給你。遵循這些原則,可以讓你的函式更方便呼叫、理解。

讓我們設想這樣一個函式,該函式返回陣列或者字串的長度:

function len(s: string): number;
function len(arr: any[]): number;
function len(x: any) {
  return x.length;
}

這個函式程式碼功能實現了,也沒有什麼報錯,但我們不能傳入一個可能是字串或者是陣列的值,因為 TypeScript 只能一次用一個函式過載處理一次函式呼叫。

len(""); // OK
len([0]); // OK
len(Math.random() > 0.5 ? "hello" : [0]);
No overload matches this call.
  Overload 1 of 2, '(s: string): number', gave the following error.
    Argument of type 'number[] | "hello"' is not assignable to parameter of type 'string'.
      Type 'number[]' is not assignable to type 'string'.
  Overload 2 of 2, '(arr: any[]): number', gave the following error.
    Argument of type 'number[] | "hello"' is not assignable to parameter of type 'any[]'.
      Type 'string' is not assignable to type 'any[]'.

因為兩個函式過載都有相同的引數數量和相同的返回型別,我們可以寫一個無過載版本的函式替代:

function len(x: any[] | string) {
  return x.length;
}

這樣函式就可以傳入兩個型別中的任意一個了。

儘可能的使用聯合型別替代過載

在函式中宣告 this (Declaring this in a Function)

TypeScript 會通過程式碼流分析函式中的 this 會是什麼型別,舉個例子:

const user = {
  id: 123,
 
  admin: false,
  becomeAdmin: function () {
    this.admin = true;
  },
};

TypeScript 能夠理解函式 user.becomeAdmin 中的 this 指向的是外層的物件 user,這已經可以應付很多情況了,但還是有一些情況需要你明確的告訴 TypeScript this 到底代表的是什麼。

在 JavaScript 中,this 是保留字,所以不能當做引數使用。但 TypeScript 可以允許你在函式體內宣告 this 的型別。

interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}
 
const db = getDB();
const admins = db.filterUsers(function (this: User) {
  return this.admin;
});

這個寫法有點類似於回撥風格的 API。注意你需要使用 function 的形式而不能使用箭頭函式:

interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}
 
const db = getDB();
const admins = db.filterUsers(() => this.admin);
// The containing arrow function captures the global value of 'this'.
// Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.

其他需要知道的型別(Other Types to Know About)

這裡介紹一些也會經常出現的型別。像其他的型別一樣,你也可以在任何地方使用它們,但它們經常與函式搭配使用。

void

void 表示一個函式並不會返回任何值,當函式並沒有任何返回值,或者返回不了明確的值的時候,就應該用這種型別。

// The inferred return type is void
function noop() {
  return;
}

在 JavaScript 中,一個函式並不會返回任何值,會隱式返回 undefined,但是 voidundefined 在 TypeScript 中並不一樣。在本文的最後會有更詳細的介紹。

void 跟 undefined 不一樣

object

這個特殊的型別 object 可以表示任何不是原始型別(primitive)的值 (stringnumberbigintbooleansymbolnullundefined)。object 不同於空物件型別 { },也不同於全域性型別 Object。很有可能你也用不到 Object

object 不同於 Object ,總是用 object!

注意在 JavaScript 中,函式就是物件,他們可以有屬性,在他們的原型鏈上有 Object.prototype,並且 instanceof Object。你可以對函式使用 Object.keys 等等。由於這些原因,在 TypeScript 中,函式也被認為是 object

unknown

unknown 型別可以表示任何值。有點類似於 any,但是更安全,因為對 unknown 型別的值做任何事情都是不合法的:

function f1(a: any) {
  a.b(); // OK
}
function f2(a: unknown) {
  a.b();
  // Object is of type 'unknown'.
}

有的時候用來描述函式型別,還是蠻有用的。你可以描述一個函式可以接受傳入任何值,但是在函式體內又不用到 any 型別的值。

你可以描述一個函式返回一個不知道什麼型別的值,比如:

function safeParse(s: string): unknown {
  return JSON.parse(s);
}
 
// Need to be careful with 'obj'!
const obj = safeParse(someRandomString);

never

一些函式從來不返回值:

function fail(msg: string): never {
  throw new Error(msg);
}

never 型別表示一個值不會再被觀察到 (observed)。

作為一個返回型別時,它表示這個函式會丟一個異常,或者會結束程式的執行。

當 TypeScript 確定在聯合型別中已經沒有可能是其中的型別的時候,never 型別也會出現:

function fn(x: string | number) {
  if (typeof x === "string") {
    // do something
  } else if (typeof x === "number") {
    // do something else
  } else {
    x; // has type 'never'!
  }
}

Function

在 JavaScript,全域性型別 Function 描述了 bindcallapply 等屬性,以及其他所有的函式值。

它也有一個特殊的性質,就是 Function 型別的值總是可以被呼叫,結果會返回 any 型別:

function doSomething(f: Function) {
  f(1, 2, 3);
}

這是一個無型別函式呼叫 (untyped function call),這種呼叫最好被避免,因為它返回的是一個不安全的 any型別。

如果你準備接受一個黑盒的函式,但是又不打算呼叫它,() => void 會更安全一些。

剩餘引數(Rest Parameters and Arguments)

parametersarguments

argumentsparameters 都可以表示函式的引數,由於本節內容做了具體的區分,所以我們定義 parameters 表示我們定義函式時設定的名字即形參,arguments 表示我們實際傳入函式的引數即實參。

剩餘引數(Rest Parameters)

除了用可選引數、過載能讓函式接收不同數量的函式引數,我們也可以通過使用剩餘引數語法(rest parameters),定義一個可以傳入數量不受限制的函式引數的函式:

剩餘引數必須放在所有引數的最後面,並使用 ... 語法:

function multiply(n: number, ...m: number[]) {
  return m.map((x) => n * x);
}
// 'a' gets value [10, 20, 30, 40]
const a = multiply(10, 1, 2, 3, 4);

在 TypeScript 中,剩餘引數的型別會被隱式設定為 any[] 而不是 any,如果你要設定具體的型別,必須是 Array<T> 或者 T[]的形式,再或者就是元祖型別(tuple type)。

剩餘引數(Rest Arguments)

我們可以藉助一個使用 ... 語法的陣列,為函式提供不定數量的實參。舉個例子,陣列的 push 方法就可以接受任何數量的實參:

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
arr1.push(...arr2);

注意一般情況下,TypeScript 並不會假定陣列是不變的(immutable),這會導致一些意外的行為:

// 型別被推斷為 number[] -- "an array with zero or more numbers",
// not specifically two numbers
const args = [8, 5];
const angle = Math.atan2(...args);
// A spread argument must either have a tuple type or be passed to a rest parameter.

修復這個問題需要你寫一點程式碼,通常來說, 使用 as const 是最直接有效的解決方法:

// Inferred as 2-length tuple
const args = [8, 5] as const;
// OK
const angle = Math.atan2(...args);

通過 as const 語法將其變為只讀元組便可以解決這個問題。

注意當你要執行在比較老的環境時,使用剩餘引數語法也許需要你開啟 [downlevelIteration](https://www.typescriptlang.org/tsconfig#downlevelIteration) ,將程式碼轉換為舊版本的 JavaScript。

引數解構(Parameter Destructuring)

你可以使用引數解構方便的將作為引數提供的物件解構為函式體內一個或者多個區域性變數,在 JavaScript 中,是這樣的:

function sum({ a, b, c }) {
  console.log(a + b + c);
}
sum({ a: 10, b: 3, c: 9 });

在解構語法後,要寫上物件的型別註解:

function sum({ a, b, c }: { a: number; b: number; c: number }) {
  console.log(a + b + c);
}

這個看起來有些繁瑣,你也可以這樣寫:

// 跟上面是有一樣的
type ABC = { a: number; b: number; c: number };
function sum({ a, b, c }: ABC) {
  console.log(a + b + c);
}

函式的可賦值性 (Assignability of Functions)

返回 void

函式有一個 void 返回型別,會產生一些意料之外,情理之中的行為。

當基於上下文的型別推導(Contextual Typing)推匯出返回型別為 void 的時候,並不會強制函式一定不能返回內容。換句話說,如果這樣一個返回 void 型別的函式型別 (type vf = () => void)
當被應用的時候,也是可以返回任何值的,但返回的值會被忽略掉。

因此,下面這些() => void 型別的實現都是有效的:

type voidFunc = () => void;
 
const f1: voidFunc = () => {
  return true;
};
 
const f2: voidFunc = () => true;
 
const f3: voidFunc = function () {
  return true;
};

而且即便這些函式的返回值賦值給其他變數,也會維持 void 型別:

const v1 = f1();
 
const v2 = f2();
 
const v3 = f3();

正是因為這個特性的存在,所以接下來的程式碼才會是有效的:

const src = [1, 2, 3];
const dst = [0];
 
src.forEach((el) => dst.push(el));

儘管 Array.prototype.push 返回一個數字,並且 Array.prototype.forEach 方法期待一個返回 void 型別的函式,但這段程式碼依然沒有報錯。就是因為基於上下文推導,推匯出 forEach 函式返回型別為 void,正是因為不強制函式一定不能返回內容,所以上面這種 return dst.push(el) 的寫法才不會報錯。

另外還有一個特殊的例子需要注意,當一個函式字面量定義返回一個 void 型別,函式是一定不能返回任何東西的:

function f2(): void {
  // @ts-expect-error
  return true;
}
 
const f3 = function (): void {
  // @ts-expect-error
  return true;
};

TypeScript 系列

冴羽的全系列文章地址:https://github.com/mqyqingfeng/Blog

TypeScript 系列是一個我都不知道要寫什麼的系列文章,如果你對於 TypeScript 有什麼困惑或者想要了解的內容,歡迎與我交流,微信:「mqyqingfeng」,公眾號:「冴羽的JavaScript部落格」或者「yayujs」

如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎 star,對作者也是一種鼓勵。

相關文章