一文學懂 TypeScript 的類型

翻譯:瘋狂的技術宅
原文: http://2ality.com/2018/04/typ...

本文首發微信公衆號:jingchengyideng
歡迎關注,天天都給你推送新鮮的前端技術文章html


你將學到什麼

閱讀本文後,你應該可以理解如下代碼的含義:前端

interface Array<T> {
  concat(...items: Array<T[] | T>): T[];
  reduce<U>(
    callback: (state: U, element: T, index: number, array: T[]) => U,
    firstState?: U): U;
  ···
}

若是你認爲這段代碼很是神祕 —— 那麼我贊成你的意見。可是(我但願證實)這些符號仍是相對容易學習的。一旦你能理解它們,就能立刻全面、精確的理解這種代碼,從而無需再去閱讀冗長的英文說明。git

運行代碼案例

TypeScript 有一個在線運行環境。爲了獲得最全面的信息,你應該在 「Options」 菜單中打開全部選項開關。這至關於在 --strict 模式下運行TypeScript編譯器。程序員

關於類型檢查的詳細說明

我在用 TypeScript 時老是喜歡打開 --strict 開關設置。沒有它,程序可能會稍微好寫一點,可是你也失去了靜態類型檢查的好處。目前此設置可以開啓如下子設置:es6

  • --noImplicitAny:若是 TypeScript 沒法推斷類型,則必須指定它。這主要用於函數和方法的參數:使用此設置,你必須對它們進行註釋。github

    • --noImplicitThis:若是 this 的類型不清楚則會給出提示信息。
    • --alwaysStrict:儘量使用 JavaScript 的嚴格模式。
    • --strictNullChecksnull 不屬於任何類型(除了它本身的類型,null),若是它是可接受的值,則必須明確指定。
    • --strictFunctionTypes:對函數類型更加嚴格的檢查。
    • --strictPropertyInitialization:若是屬性的值不能是 undefined ,那麼它必須在構造函數中進行初始化。

更多信息:TypeScript 手冊中的「編譯器選項」一章。面試

類型

在本文中,咱們把類型看做是一組值的集合。 JavaScript 語言(不是TypeScript!)有7種類型:typescript

  • Undefined:具備惟一元素 undefined 的集合。
  • Null:具備惟一元素「null」的集合。
  • Boolean:具備兩個元素 falsetrue 的集合。
  • Number:全部數字的集合。
  • String:全部字符串的集合。
  • Symbol:全部符號的集合。
  • Object:全部對象的集合(包括函數和數組)。

全部這些類型都是 dynamic:能夠用在運行時。編程

TypeScript 爲 JavaScript 帶來了額外的層:靜態類型。這些僅在編譯或類型檢查源代碼時存在。每一個存儲位置(變量或屬性)都有一個靜態類型,用於預測其動態值。類型檢查可確保這些預測可以實現。還有不少能夠進行 靜態 檢查(不運行代碼)的東西。例如,若是函數 f(x) 的參數 x 是靜態類型 number,則函數調用 f('abc') 是非法的,由於參數 'abc' 是錯誤的靜態類型。segmentfault

類型註釋

變量名後的冒號開始 類型註釋:冒號後的類型簽名用來描述變量能夠接受的值。例如以代碼告訴 TypeScript 變量 「x」 只能存儲數字:

let x: number;

你可能想知道用 undefined 去初始化 x 是否是違反了靜態類型。 TypeScript 不會容許這種狀況出現,由於在爲它賦值以前不容許操做 x

類型推斷

即便在 TypeScript 中每一個存儲位置都有靜態類型,你也沒必要老是明確的去指定它。 TypeScript 一般能夠對它的類型進行推斷。例如若是你寫下這行代碼:

let x = 123;

而後 TypeScript 會推斷出 x 的靜態類型是 number

類型描述

在類型註釋的冒號後面出現的是所謂的類型表達式。這些範圍從簡單到複雜,並按以下方式建立。

基本類型是有效的類型表達式:

  • 對應 JavaScript 動態類型的靜態類型:

    - `undefined`, `null`
    - `boolean`, `number`, `string`
    - `symbol`
    - `object`
    • 注意:值 undefined 與類型 undefined(取決於所在的位置)
  • TypeScript 的特定類型:

    • Array(從技術上講不是 JS 中的類型)
    • any(全部值的類型)
    • 等等其餘類型

請注意,「undefined做爲值「 和 」undefined做爲類型」 都寫作 undefined。根據你使用它的位置,被解釋爲值或類型。 null 也是如此。

你能夠經過類型運算符對基本類型進行組合的方式來建立更多的類型表達式,這有點像使用運算符 union)和intersection()去合併集合。

下面介紹 TypeScript 提供的一些類型運算符。

數組類型

數組在 JavaScript 中扮演如下兩個角色(有時是二者的混合):

  • 列表:全部元素都具備相同的類型。數組的長度各不相同。
  • 元組:數組的長度是固定的。元素不必定具備相同的類型。

數組做爲列表

數組 arr 被用做列表有兩種方法表示 ,其元素都是數字:

let arr: number[] = [];
let arr: Array<number> = [];

一般若是存在賦值的話,TypeScript 就能夠推斷變量的類型。在這種狀況下,實際上你必須幫它解決類型問題,由於在使用空數組時,它沒法肯定元素的類型。

稍後咱們將回到尖括號表示法(Array<number>)。

數組做爲元組

若是你想在數組中存儲二維座標點,那麼就能夠把這個數組看成元組去用。看上去是這個樣子:

let point: [number, number] = [7, 5];

在這種狀況下,你不須要類型註釋。

另一個例子是 Object.entries(obj) 的返回值:一個帶有一個 [key,value] 對的數組,它用於描述 obj 的每一個屬性。

> Object.entries({a:1, b:2})
[ [ 'a', 1 ], [ 'b', 2 ] ]

Object.entries() 的返回值類型是:

Array<[string, any]>

函數類型

如下是函數類型的例子:

(num: number) => string

這個類型是一個函數,它接受一個數字類型參數而且返回值爲字符串。在類型註釋中使用這種類型(String 在這裏是個函數)的例子:

const func: (num: number) => string = String;

一樣,咱們通常不會在這裏使用類型註釋,由於 TypeScript 知道 String 的類型,所以能夠推斷出 func 的類型。

如下代碼是一個更實際的例子:

function stringify123(callback: (num: number) => string) {
  return callback(123);
}

因爲咱們使用了函數類型來描述 stringify123() 的參數 callback,因此TypeScript 拒絕如下函數調用。

f(Number);

但它接受如下函數調用:

f(String);

函數聲明的返回類型

對函數的全部參數進行註釋是一個很好的作法。你還能夠指定返回值類型(不過 TypeScript 很是擅長去推斷它):

function stringify123(callback: (num: number) => string): string {
  const num = 123;
  return callback(num);
}

特殊返回值類型 void

void 是函數的特殊返回值類型:它告訴 TypeScript 函數老是返回 undefined(顯式或隱式):

function f1(): void { return undefined } // OK
function f2(): void { } // OK
function f3(): void { return 'abc' } // error

可選參數

標識符後面的問號表示該參數是可選的。例如:

function stringify123(callback?: (num: number) => string) {
  const num = 123;
  if (callback) {
    return callback(num); // (A)
  }
  return String(num);
}

--strict 模式下運行 TypeScript 時,若是事先檢查時發現 callback 沒有被省略,它只容許你在 A 行進行函數調用。

參數默認值

TypeScript支持 ES6 參數默認值

function createPoint(x=0, y=0) {
  return [x, y];
}

默認值可使參數可選。一般能夠省略類型註釋,由於 TypeScript 能夠推斷類型。例如它能夠推斷出 xy 都是 number 類型。

若是要添加類型註釋,應該這樣寫:

function createPoint(x:number = 0, y:number = 0) {
  return [x, y];
}

rest 類型

你還能夠用 ES6 rest operator 進行 TypeScript 參數定義。相應參數的類型必須是數組:

function joinNumbers(...nums: number[]): string {
    return nums.join('-');
}
joinNumbers(1, 2, 3); // '1-2-3'

Union

在JavaScript中,有時候變量會是有幾種類型之中的一種。要描述這些變量,可使用 union types。例如,在下面的代碼中,xnull 類型或 number 類型:

let x = null;
x = 123;

x 的類型能夠描述爲 null | number

let x: null|number = null;
x = 123;

類型表達式 s | t 的結果是類型 st 在集合理論意義上的聯合(正如咱們以前看到的那樣,兩個集合)。

下面讓咱們重寫函數 stringify123():此次咱們不但願參數 callback 是可選的。應該老是調用它。若是調用者不想傳入一個函數,則必須顯式傳遞 null。實現以下。

function stringify123(
  callback: null | ((num: number) => string)) {
  const num = 123;
  if (callback) { // (A)
    return callback(123); // (B)
  }
  return String(num);
}

請注意,在行 B 進行函數調用以前,咱們必須再次檢查 callback 是否真的是一個函數(行A)。若是沒有檢查,TypeScript 將會報告錯誤。

Optional 與 undefined|T

類型爲 T 的可選參數和類型爲 undefined|T 的參數很是類似。 (另外對於可選屬性也是如此。)

主要區別在於你能夠省略可選參數:

function f1(x?: number) { }
f1(); // OK
f1(undefined); // OK
f1(123); // OK

But you can’t omit parameters of type
可是你不能省略 undefined|T 類型的參數:

function f2(x: undefined | number) { }
f2(); // error
f2(undefined); // OK
f2(123); // OK

nullundefined 一般不包含在類型中

在許多編程語言中,null 是全部類型的一部分。例如只要 Java 中的參數類型爲 String,就能夠傳遞 null 而Java 不會報錯。

相反,在TypeScript中,undefinednull 由單獨的不相交類型處理。若是你想使它們生效,必需要有一個類型聯合,如 undefined|stringnull|string

對象

與Arrays相似,對象在 JavaScript 中扮演兩個角色(偶爾混合和/或更加動態):

  • 記錄:在開發時已知的固定數量的屬性。每一個屬性能夠有不一樣的類型。
  • 字典:在開發時名稱未知的任意數量的屬性。全部屬性鍵(字符串和/或符號)都具備相同的類型,屬性值也是如此。

咱們將在本文章中忽略 object-as-dictionaries。順便說一句,不管如何,map 一般是比字典的更好選擇。

經過接口描述 objects-as-records

接口描述 objects-as-records 。例如:

interface Point {
  x: number;
  y: number;
}

TypeScript 類型系統的一大優點在於它的結構上,而不是在命名上。也就是說,接口 Point 可以匹配適當結構的全部對象:

function pointToString(p: Point) {
  return `(${p.x}, ${p.y})`;
}
pointToString({x: 5, y: 7}); // '(5, 7)'

相比之下,Java 的標稱類型系統須要類來實現接口。

可選屬性

若是能夠省略屬性,則在其名稱後面加上一個問號:

interface Person {
  name: string;
  company?: string;
}

方法

接口內還能夠包含方法:

interface Point {
  x: number;
  y: number;
  distance(other: Point): number;
}

類型變量和泛型類型

使用靜態類型,能夠有兩個級別:

  • 值存在於對象級別
  • 類型存在於元級別

同理:

  • 普通變量定義在對象級別之上。
  • 類型變量存在於元級別之上。它們是值爲類型的變量。

普通變量經過 constlet 等引入。類型變量經過尖括號( <> )引入。例如如下代碼包含類型變量 T,經過 <T> 引入。

interface Stack<T> {
  push(x: T): void;
  pop(): T;
}

你能夠看到類型參數 TStack 的主體內出現兩次。所以,該接口能夠直觀地理解以下:

  • Stack 是一堆值,它們都具備給定的類型 T。每當你提到 Stack 時,必須寫 T。接下來咱們會看到究竟該怎麼用。
  • 方法 .push() 接受類型爲 T 的值。
  • 方法 .pop() 返回類型爲 T 的值。

若是使用 Stack,則必須爲 T 指定一個類型。如下代碼顯示了一個虛擬棧,其惟一目的是匹配接口。

const dummyStack: Stack<number> = {
  push(x: number) {},
  pop() { return 123 },
};

例子:map

map 在 TypeScript 中的定義。例如:

const myMap: Map<boolean,string> = new Map([
  [false, 'no'],
  [true, 'yes'],
]);

函數的類型變量

函數(和方法)也能夠引入類型變量:

function id<T>(x: T): T {
  return x;
}

你能夠按如下方式使用此功能。

id<number>(123);

因爲類型推斷,還能夠省略類型參數:

id(123);

傳遞類型參數

函數能夠將其她的類型參數傳給接口、類等:

function fillArray<T>(len: number, elem: T) {
  return new Array<T>(len).fill(elem);
}

類型變量 T 在這段代碼中出現三次:

  • fillArray<T>:引入類型變量
  • elem:T:使用類型變量,從參數中選擇它。
  • Array<T>:將 T 傳遞給 Array 的構造函數。

這意味着:咱們沒必要顯式指定Array<T>的類型 T —— 它是從參數 elem中推斷出來的:

const arr = fillArray(3, '*');
  // Inferred type: string[]

總結

讓咱們用前面學到的知識來理解最開始看到的那段代碼:

interface Array<T> {
  concat(...items: Array<T[] | T>): T[];
  reduce<U>(
    callback: (state: U, element: T, index: number, array: T[]) => U,
    firstState?: U): U;
  ···
}

這是一個Array的接口,其元素類型爲 T,每當使用這個接口時必須填寫它:

  • 方法.concat()有零個或多個參數(經過 rest 運算符定義)。其中每個參數中都具備類型 T[]|T。也就是說,它是一個 T 類型的數組或是一個 T 值。
  • 方法.reduce() 引入了本身的類型變量 UU 表示如下實體都具備相同的類型(你不須要指定,它是自動推斷的):

    • Parameter state of callback() (which is a function)
    • statecallback() 的參數(這是一個函數)

      • Result of callback()
    • callback()的返回
    • .reduce()的可選參數 firstState

      • Result of .reduce()
    • .reduce()的返回

callback 還將得到一個 element 參數,其類型與 Array 元素具備相同的類型 T,參數 index 是一個數字,參數 arrayT 的值。

擴展閱讀


  • 本文首發微信公衆號:jingchengyideng

    歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

    歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章



    歡迎繼續閱讀本專欄其它高贊文章:

    相關文章
    相關標籤/搜索