typescript返回值類型和參數類型的具體使用

返回值類型

在 JavaScript 中,我們知道一個函數可以沒有顯式 return,此時函數的返回值應該是 undefined:

function fn() {
  // TODO
}
console.log(fn()); // => undefined

需要註意的是,在 TypeScript 中,如果我們顯式聲明函數的返回值類型為 undfined,將會得到如下所示的錯誤提醒。

function fn(): undefined { // ts(2355) A function whose declared type is neither 'void' nor 'any' must return a value
  // TODO
}

此時,正確的做法是使用 void 類型來表示函數沒有返回值的類型(這是“廢柴” void 類型唯一有用的場景),示例如下:

function fn1(): void {
}
fn1().doSomething(); // ts(2339) Property 'doSomething' does not exist on type 'void'

我們可以使用類似定義箭頭函數的語法來表示函數類型的參數和返回值類型,此時=> 類型僅僅用來定義一個函數類型而不用實現這個函數。

需要註意的是,這裡的=>與 ES6 中箭頭函數的=>有所不同。TypeScript 函數類型中的=>用來表示函數的定義,其左側是函數的參數類型,右側是函數的返回值類型;而 ES6 中的=>是函數的實現。

在對象(即接口類型)中,除瞭使用這種聲明語法,我們還可以使用類似對象屬性的簡寫語法來聲明函數類型的屬性,如:

interface Entity {
    add: (a: number, b: number) => number;
    del(a: number, b: number): number;
}
const entity: Entity = {
    add: (a, b) => a + b,
    del(a, b) {
      return a - b;
    },
};

在某種意義上來說,這兩種形式都是等價的。但是很多時候,我們不必或者不能顯式地指明返回值的類型,這就涉及可缺省和可推斷的返回值類型的講解。

可缺省和可推斷的返回值類型

幸運的是,函數返回值的類型可以在 TypeScript 中被推斷出來,即可缺省。

函數內是一個相對獨立的上下文環境,我們可以根據入參對值加工計算,並返回新的值。從類型層面看,我們也可以通過類型推斷加工計算入參的類型,並返回新的類型,示例如下:

function computeTypes(one: string, two: number) {
  const nums = [two];
  const strs = [one]
  return {
    nums,
    strs
  } // 返回 { nums: number[]; strs: string[] } 的類型 
}

請記住:這是一個很重要也很有意思的特性,函數返回值的類型推斷結合泛型可以實現特別復雜的類型推斷,比如 Redux Model 中 State、Reducer、Effect 類型的關聯。

一般情況下,TypeScript 中的函數返回值類型是可以缺省和推斷出來的,但是有些特例需要我們顯式聲明返回值類型,比如 Generator 函數的返回值。

Generator 函數的返回值

ES6 中新增的 Generator 函數在 TypeScript 中也有對應的類型定義。

Generator 函數返回的是一個 Iterator 迭代器對象,我們可以使用 Generator 的同名接口泛型或者 Iterator 的同名接口泛型表示返回值的類型(Generator 類型繼承瞭 Iterator 類型),示例如下:

type AnyType = boolean;
type AnyReturnType = string;
type AnyNextType = number;
function *gen(): Generator<AnyType, AnyReturnType, AnyNextType> {
  const nextValue = yield true; // nextValue 類型是 number,yield 後必須是 boolean 類型
  return `${nextValue}`; // 必須返回 string 類型
}

註意:TypeScript 3.6 之前的版本不支持指定 next、return 的類型,所以在某些有點歷史的代碼中,我們可能會看到 Generator 和 Iterator 類型不一樣的表述。

參數類型

關於 JS 中函數的參數,在es6之後,統共可以分為:可選參數、默認參數、剩餘參數 幾個特性。

可選參數和默認參數

在實際工作中,我們可能經常碰到函數參數可傳可不傳的情況,當然 TypeScript 也支持這種函數類型表達,如下代碼所示:

function log(x?: string) {
  return x;
}
log(); // => undefined
log('hello world'); // => hello world

在上述代碼中,我們在類型標註的:前添加?表示 log 函數的參數 x 就是可缺省的。

也就是說參數 x 的類型可能是 undefined(第 5 行調用 log 時不傳入實參)類型或者是 string 類型(第 6 行調用 log 傳入 'hello world' 實參),那是不是意味著可缺省和類型是 undefined 等價呢?

function log(x?: string) {
  console.log(x);
}
function log1(x: string | undefined) {
  console.log(x);
}
log();
log(undefined);
log1(); // ts(2554) Expected 1 arguments, but got 0
log1(undefined);

顯而易見:這裡的 ?: 表示參數可以缺省、可以不傳,但如果傳則一定是string類型。也就是說調用函數時,我們可以不顯式傳入參數。但是,如果我們聲明瞭參數類型為 xxx | undefined,就表示函數參數是不可缺省且類型必須是 xxx 或者 undfined。

因此,在上述代碼中,log1 函數如果不顯示傳入函數的參數,TypeScript 就會報一個 ts(2554) 的錯誤,即函數需要 1 個參數,但是我們隻傳入瞭 0 個參數。

在 ES6 中支持函數默認參數的功能。而 TypeScript 會根據函數的默認參數的類型來推斷函數參數的類型,示例如下:

function log(x = 'hello') {
    console.log(x);
}
log(); // => 'hello'
log('hi'); // => 'hi'
log(1); // ts(2345) Argument of type '1' is not assignable to parameter of type 'string | undefined'

在上述示例中,根據函數的默認參數 'hello' ,TypeScript 推斷出瞭 x 的類型為 string | undefined。

當然,對於默認參數,TypeScript 也可以顯式聲明參數的類型(一般默認參數的類型是參數類型的子集時,我們才需要這麼做)。不過,此時的默認參數隻起到參數默認值的作用,如下代碼所示:

function log1(x: string = 'hello') {
    console.log(x);
}
// ts(2322) Type 'string' is not assignable to type 'number'
function log2(x: number = 'hello') {
    console.log(x);
}
log2();
log2(1);
log2('1'); // ts(2345) Argument of type '"1"' is not assignable to parameter of type 'number | undefined'

上例函數 log2 中,我們顯式聲明瞭函數參數 x 的類型為 number,表示函數參數 x 的類型可以不傳或者是 number 類型。因此,如果我們將默認值設置為字符串類型,編譯器就會拋出一個 ts(2322) 的錯誤。

同理,如果我們將函數的參數傳入瞭字符串類型,編譯器也會拋出一個 ts(2345) 的錯誤。

請再次註意:函數的默認參數類型必須是參數類型的子類型:

function log3(x: number | string = 'hello') {
    console.log(x);
}

在上述代碼中,函數 log3 的函數參數 x 的類型為可選的聯合類型 number | string,但是因為默認參數字符串類型是聯合類型 number | string 的子類型,所以 TypeScript 也會檢查通過。

剩餘參數

在 ES6 中,JavaScript 支持函數參數的剩餘參數,它可以把多個參數收集到一個變量中。同樣,在TypeScript 中也支持這樣的參數類型定義,如下代碼所示:

function sum(...nums: number[]) {
    return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2); // => 3
sum(1, 2, 3); // => 6
sum(1, '2'); // ts(2345) Argument of type 'string' is not assignable to parameter of type 'number'

在上述代碼中,sum 是一個求和的函數,…nums將函數的所有參數收集到瞭變量 nums 中,而 nums 的類型應該是 number[],表示所有被求和的參數是數字類型。因此,sum(1, '2') 拋出瞭一個 ts(2345) 的錯誤,因為參數 '2' 並不是 number 類型。

如果我們將函數參數 nums 聚合的類型定義為 (number | string)[],如下代碼所示:

function sum(...nums: (number | string)[]): number {
    return nums.reduce<number>((a, b) => a + Number(b), 0);
}
sum(1, '2', 3); // 6

那麼,函數的每一個參數的類型就是聯合類型 number | string,因此 sum(1, '2', 3) 的類型檢查也就通過瞭。

typescript中的常見報錯:

  • ts(2322)是一個靜態類型檢查的錯誤,在註解的類型和賦值的類型不同的時候就會拋出這個錯誤
  • ts(2355) :簡單來說就是:函數必須有一個返回值(報錯翻譯:聲明類型既不是void也不是any的函數必須返回一個值)
  • ts(2339): 在TypeScript中如果按JS的方式去獲取對象屬性時,可能會遇到。它表示Typescript在執行代碼檢查時在該對象沒有定義相應屬性。解決方法有 —— 直接設置對象為any、對象斷言為any、將屬性.獲取方式改為數組式獲取三種
  • ts(2345):參數類型與預期不符
  • ts(2554):參數個數與預期不符

到此這篇關於typescript返回值類型和參數類型的具體使用的文章就介紹到這瞭,更多相關typescript返回值類型和參數類型內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: