TypeScript 徹底手冊

The Definitive TypeScript Handbook

Stack Overflow 在 90,000 名開發者中開展了一項調查,結果顯示 TypeScript 是人們最想學習的工具之一。html

在過去幾年中,TypeScript 的熱門程度、社區規模和使用率都在不斷提升。現在,甚至 Facebook 正將 Jest 項目轉移至 TypeScriptreact

什麼是 TypeScript?

TypeScript 是 JavaScript 的超集,具備靜態類型特性,旨在簡化大型 JavaScript 應用程序的開發,也被稱爲 JavaScript that scales可拓展的 JavaScript)。git

爲何要用 TypeScript?

JavaScript 在過去幾年中快速發展,成爲客戶端和服務器端最通用的跨平臺語言。github

但 JavaScript 本意並不用於大型應用開發。它是一種沒有類型系統的動態語言,也就是說,變量的值能夠是任何類型(例如字符串或布爾值)。typescript

而類型系統可以提升代碼質量和可讀性,使代碼庫更易於維護或重構。更重要的是它能夠在編譯時就捕獲錯誤,而不是在運行時才捕獲。數組

而 JavaScript 並無類型系統,因此一個大型開發團隊難以使用 JavaScript 構建複雜的應用程序。安全

而 TypeScript 能在編譯時檢查不一樣部分代碼的正確性。在編譯時檢查出錯誤,便於開發者發現錯誤的位置和具體問題。若是運行時才檢查出錯誤,則須要跟蹤複雜的堆棧,花費大量時間進行調試。bash

TypeScript 的優勢

  1. 在開發週期中能更早地捕獲潛在的錯誤。
  2. 管理大型代碼庫。
  3. 更易於重構。
  4. 更易於團隊合做:代碼的耦合性越強,不一樣開發人員訪問代碼庫時越不容易形成無心破壞。
  5. 文檔特性:類型自己就是一種文檔信息,方便往後開發者本人或者其餘開發者查詢。

TypeScript 的缺點

  1. 須要額外的學習:須要在短時間放緩進度與長期提升效率間進行權衡。
  2. 類型錯誤可能多種多樣。
  3. 配置極大地影響運行。

類型

Boolean (布爾值)

const isLoading: boolean = false;

複製代碼

Number (數字)

const decimal: number = 8;
const binary: number = 0b110;

複製代碼

String (字符串)

const fruit: string = "orange";

複製代碼

Array (數組)

數組能夠寫成下面兩種形式:服務器

// 最多見的方式
let firstFivePrimes: number[] = [2, 3, 5, 7, 11];
// 不太常見的方式:使用泛型 (稍後介紹)
let firstFivePrimes2: Array<number> = [2, 3, 5, 7, 11];

複製代碼

Tuple (元組)

Tuple 類型表示一種組織好的數組,元素的類型預先知道,而且數量固定。這意味着你有可能獲得錯誤提示:dom

let contact: [string, number] = ['John', 954683];
contact = ['Ana', 842903, 'extra argument']  /* Error! 
Type '[string, number, string]' is not assignable to type '[string, number]'. */

複製代碼

Any (任意值)

any 與類型系統中的任何類型都兼容。意味着能夠將任何內容賦值給它,也能夠將它賦值給任何類型。它能讓你避開類型檢查。

let variable: any = 'a string';
variable = 5;
variable = false;
variable.someRandomMethod(); /* 行吧,
也許運行的時候 someRandomMethod 是存在的 */

複製代碼

Void (空值)

void 表示沒有任何類型。它一般用做沒有返回值的函數的返回類型。

function sayMyName(name: string): void {
  console.log(name);
}
sayMyName('Heisenberg');

複製代碼

Never

never 類型表示的是那些永不存在的值的類型。 例如,never 類型是那些老是會拋出異常、或者根本就不會有返回值的函數的返回值類型。

// 拋出異常
function error(message: string): never {
  throw new Error(message);
}


複製代碼

Null 和 Undefined

undefinednull 二者各自有本身的類型分別叫作 undefinednull。和 void 類似,它們的自己的類型用處不是很大,可是在聯合類型中很是有用 (稍後介紹)

type someProp = string | null | undefined;

複製代碼

Unknown

TypeScript 3.0 引入了 unknown (未知) 類型,它是與 any 類型對應的安全類型。任何東西均可以賦值給 unknown,但 unknown 不能賦值給除了它自己和 any 之外的任何東西。在沒有先斷言或指定到更具體類型的狀況下,不容許對 unknown 進行任何操做。

type I1 = unknown & null;    // null
type I2 = unknown & string;  // string
type U1 = unknown | null;    // unknown
type U2 = unknown | string;  // unknown

複製代碼

類型別名

類型別名能夠爲現有類型提供替代名稱,以便某些地方使用。構造它的語法以下:

type Login = string;

複製代碼

聯合類型

TypeScript 容許讓一個屬性具備多種數據類型,名爲 union (聯合) 類型。

type Password = string | number;

複製代碼

交叉類型

交叉類型是將多種類型疊加到一塊兒成爲一種類型。

interface Person {
  name: string;
  age: number;
}
interface Worker {
  companyId: string;
}
type Employee = Person & Worker;

複製代碼

Interface (接口)

接口好似你和編譯器定義契約,由你指定一個類型,預期它的屬性應該是些什麼類型。

邊注:接口不受 JavaScript 運行時的特性影響,它只在類型檢查中會用到。

  • 能夠聲明可選屬性(帶有 ? 標記),意味着接口的對象可能會、也可能不會定義這些屬性。
  • 能夠聲明只讀屬性,意味着一旦爲屬性賦值,就沒法更改。
interface ICircle {
  readonly id: string;
  center: {
    x: number;
    y: number;
  },
  radius: number;
  color?: string;  // 可選屬性
}
const circle1: ICircle = {
  id: '001',
  center: { x: 0 },
  radius: 8,
};  /* Error! Property 'y' is missing in type '{ x: number; }' 
but required in type '{ x: number; y: number; }'. */

複製代碼

擴展接口

接口能夠擴展成另外一個接口,或者更多接口。這使得接口的編寫更具備靈活性和複用性。

interface ICircleWithArea extends ICircle {
  getArea: () => number;
}

複製代碼

實現接口

實現接口的類須要嚴格遵循接口的結構。

interface IClock {
  currentTime: Date;
  setTime(d: Date): void;
}

複製代碼

枚舉

enum (枚舉) 用來組織一組的相關值,這些值能夠是數值,也能夠是字符串值。

enum CardSuit {
  Clubs,
  Diamonds,
  Hearts,
  Spades
}
let card = CardSuit.Clubs;

複製代碼

默認狀況下,枚舉的本質是數字。enum 的取值從 0 開始,以 1 遞增。

上一個例子所生成的 JavaScript 代碼以下:

var CardSuit;
(function (CardSuit) {
  CardSuit[CardSuit["Clubs"] = 0] = "Clubs";
  CardSuit[CardSuit["Diamonds"] = 1] = "Diamonds";
  CardSuit[CardSuit["Hearts"] = 2] = "Hearts";
  CardSuit[CardSuit["Spades"] = 3] = "Spades";
})(CardSuit || (CardSuit = {}));

複製代碼

或者,枚舉能夠用字符串值來初始化,這種方法更易讀。

enum SocialMedia {
  Facebook = 'FACEBOOK',
  Twitter = 'TWITTER',
  Instagram = 'INSTAGRAM',
  LinkedIn = 'LINKEDIN'
}

複製代碼

反向映射

enum 支持反向映射,也就是說,能夠經過值來得到成員、成員名。

回顧以前 CardSuit 的例子:

const clubsAsNumber: number = CardSuit.Clubs; // 3
const clubsAsString: string = CardSuit[0];    // 'Clubs'

複製代碼

函數

你能夠爲每一個參數指定一個類型,再爲函數指定一個返回類型。

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

複製代碼

函數重載

TypeScript 容許聲明 函數重載。簡單來講,可使用多個名稱相同但參數類型和返回類型不一樣的函數。參考下面的例子:

function padding(a: number, b?: number, c?: number, d?: any) {
  if (b === undefined && c === undefined && d === undefined) {
    b = c = d = a;
  }
  else if (c === undefined && d === undefined) {
    c = a;
    d = b;
  }
  return {
    top: a,
    right: b,
    bottom: c,
    left: d
  };
}

複製代碼

參數的含義根據傳遞給函數的參數數量而變化。此外,該函數只接受一個、兩個或四個參數。要構造函數重載,只需屢次聲明函數頭就能夠了。最後一個函數頭真正實現了函數體,但函數外部並不能直接調用最後一個函數頭。

function padding(all: number);
function padding(topAndBottom: number, leftAndRight: number);
function padding(top: number, right: number, bottom: number, left: number);
function padding(a: number, b?: number, c?: number, d?: number) {
  if (b === undefined && c === undefined && d === undefined) {
    b = c = d = a;
  }
  else if (c === undefined && d === undefined) {
    c = a;
    d = b;
  }
  return {
    top: a,
    right: b,
    bottom: c,
    left: d
  };
}

複製代碼

你能夠指定屬性的類型和方法參數的類型。

class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet(name: string) {
    return Hi <span class="hljs-subst" style="color: rgb(51, 51, 51); font-weight: normal;">${name}</span>, <span class="hljs-subst" style="color: rgb(51, 51, 51); font-weight: normal;">${<span class="hljs-keyword" style="color: rgb(51, 51, 51); font-weight: bold;">this</span>.greeting}</span>;
  }
}

複製代碼

訪問修飾符

Typescript 支持 public (公有), private (私有), protected (保護) 修飾符,它們決定了類成員的可訪問性。

  • public (公有) 成員和純 JavaScript 的成員同樣,是默認的修飾符。
  • private (私有) 成員對外界來講不可訪問。
  • protected(保護) 成員和私有成員的區別在於,它可以被繼承類訪問。
| 具備訪問權限     | public | protected | private |
| :------------- | :----: | :-------: | :-----: |
| 類自己          |   yes  |    yes    |   yes   |
| 派生類          |   yes  |    yes    |    no   |
| 類實例          |   yes  |     no    |    no   |

複製代碼

只讀修飾符

readonly (只讀) 變量必須在它聲明或構造時初始化。

class Spider {
  readonly name: string;
  readonly numberOfLegs: number = 8;
  constructor (theName: string) {
    this.name = theName;
  }
}

複製代碼

參數屬性

參數屬性 能夠放在一個地方建立並初始化成員。它經過給構造函數參數添加一個訪問限定符來聲明。

class Spider {
  readonly numberOfLegs: number = 8;
  constructor(readonly name: string) {
  }
}

複製代碼

抽象

abstract (抽象) 這個關鍵字能夠用在抽象類上,也能夠用在抽象類方法上。

  • 抽象類 不會直接被實例化。抽象類主要用於繼承,繼承抽象類必須實現它全部的抽象方法。
  • 抽象成員 不包含具體實現,所以不能被直接訪問。這些成員必須在派生類中實現。 (相似接口)

類型斷言

TypeScript 容許你以任何方式覆蓋其推斷的類型。當你比編譯器自己能更好地理解變量類型時,可使用它。

const friend = {};
friend.name = 'John';  // Error! Property 'name' does not exist on type '{}'
interface Person {
  name: string;
  age: number;
}

複製代碼

最初,類型斷言的語法是 <type>

let person = <Person> {};

複製代碼

但這在 JSX 中使用時產生了歧義。所以建議使用 as 代替。

類型斷言一般在從 JavaScript 遷移代碼時使用,你對變量的類型瞭解可能比當前指派的更準確。

但斷言也會 被認爲有害。

咱們來看看上一個示例中的 Person 接口,你注意到了什麼問題嗎?若是你注意到丟失了 age 屬性,恭喜,你對了!編譯器可能會幫助你自動完成 Person 的屬性,但若是您遺漏了任何屬性,它也不會報錯。

類型推論

沒有明確指定出類型時,TypeScript 會推斷變量類型。

/**

變量聲明
/
let a = "some string";
let b = 1;
a = b;  // Error! Type 'number' is not assignable to type 'string'.


複製代碼

類型兼容性

類型兼容性是基於結構類型的,結構類型只使用其成員來描述類型。

結構化類型系統的基本規則是:若是 x 要兼容 y,那麼 y 至少具備與 x 相同的屬性。

interface Person {
name: string;
}

複製代碼

因爲 y 有一個成員 name: string 匹配 Person 接口所需的屬性,這意味着 xy 的子類型。所以這個賦值是合法的。

函數

參數數量
在函數調用中,至少須要傳入足夠的參數,多餘的參數不會致使任何錯誤。

function consoleName(person: Person) {
  console.log(person.name);
}
consoleName({ name: 'John' });           // 正確
consoleName({ name: 'John', age: 20 });  // 多餘的參數也合法

複製代碼

返回值類型
返回值類型必須至少包含足夠的數據。

let x = () => ({name: 'John'});
let y = () => ({name: 'John', age: 20 });
x = y;  // 正確
y = x;  /* Error! Property 'age' is missing in type '{ name: string; }'
but required in type '{ name: string; age: number; }' */

複製代碼

類型保護

類型保護能夠在條件塊中縮小對象類型的範圍。

typeof

在條件裏使用 typeof,編譯器會知道變量的類型會不一致。在下面的示例中,TypeScript 會知道:在條件塊以外,x 多是布爾值,而布爾值上沒法調用函數 toFixed

function example(x: number | boolean) {
  if (typeof x === 'number') {
    return x.toFixed(2);
  }
  return x.toFixed(2); // Error! Property 'toFixed' does not exist on type 'boolean'.
}

複製代碼

instanceof

class MyResponse {
  header = 'header example';
  result = 'result example';
  // ...
}
class MyError {
  header = 'header example';
  message = 'message example';
  // ...
}
function example(x: MyResponse | MyError) {
  if (x instanceof MyResponse) {
    console.log(x.message); // Error! Property 'message' does not exist on type 'MyResponse'.
    console.log(x.result);  // 正確
  } else {
    // TypeScript 知道這裏必定是 MyError
    console.log(x.message); // 正確
    console.log(x.result);  // Error! Property 'result' does not exist on type 'MyError'.
  }
}

複製代碼

in

in 運算符會檢查一個屬性在某對象上是否存在。

interface Person {
  name: string;
  age: number;
}
const person: Person = {
  name: 'John',
  age: 28,
};

複製代碼

Literal Types (字面量類型)

字面量正是 JavaScript 原始數據類型具體的值,它們能夠與 union (聯合) 類型搭配使用,構造一些實用的概念。

type Orientation = 'landscape' | 'portrait';
function changeOrientation(x: Orientation) {
  // ...
}
changeOrientation('portrait'); // 正確
changeOrientation('vertical'); /* Error! Argument of type '"vertical"' is not 
assignable to parameter of type 'Orientation'. /

複製代碼

條件類型

條件類型表示類型關係的測試,並根據測試的結果選擇兩種可能類型中的一種。

type X = A extends B ? C : D;

複製代碼

若是 A 類型能夠賦值給 B 類型,那麼 XC 類型;不然 XD 類型。

泛型

泛型是必須包含或引用其餘類型才能完成的類型。它增強了變量之間有意義的約束。

下面例子中的函數會返回所傳入的任何類型的數組。

function reverse<T>(items: T[]): T[] {
  return items.reverse();
}
reverse([1, 2, 3]); // number[]
reverse([0, true]); // (number | boolean)[]

複製代碼

keyof

keyof 運算符會查詢給定類型的鍵集。

interface Person {
  name: string;
  age: number;
}
type PersonKeys = keyof Person; // 'name' | 'age'

複製代碼

映射類型

映射類型,經過在屬性類型上創建映射,從現有的類型建立新類型。具備已知類型的每一個屬性都會根據你指定的規則進行轉換。

Partial

type Partial<T> = {
  [P in keyof T]?: T[P];
}

複製代碼
  • 泛型 Partial 類型被定義時只有一個類型參數 T
  • keyof T 表示全部 T 類型屬性的名字(字符串字面類型)的聯合。
  • [P in keyof T]?: T[P] 表示全部 T 類型的屬性 P 的類型都應該是可選的,而且都會被轉換爲 T[P]
  • T[P] 表示 T 類型的屬性 P 的類型。

Readonly (只讀)

正如在接口部分中所介紹的,TypeScript 中能夠建立只讀屬性。 Readonly 類型接受一個類型 T,並將其全部屬性設置爲只讀。

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

複製代碼

Exclude

Exclude 能夠從其餘類型中排除某些類型。排除的是能夠賦值給 T 的屬性。

/**

type Exclude<T, U> = T extends U ? never : T;
/
type User = {
_id: number;
name: string;
email: string;
created: number;
};


複製代碼

Pick

Pick 能夠從其餘類型中選取某些類型。 挑選的是能夠賦值給 T 的屬性。

/**

複製代碼

infer

你可使用 infer 關鍵字來推斷條件類型的 extends 子句中的類型變量。這樣的推斷類型變量只能用於條件類型的 true 分支。

ReturnType

獲取函數的返回類型。

/**
原版的 TypeScript's ReturnType type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any; / type MyReturnType<T> = T extends (...args: any) => infer R ? R : any; 複製代碼

咱們來拆解 MyReturnType

  • T 的返回類型是 …
  • 首先,T 是否是一個函數?
  • 若是是,那麼類型解析爲推斷出的返回類型 R
  • 若是不是,類型解析爲 any

參考資料與實用連接

basarat.gitbooks.io/typescript/

www.typescriptlang.org/docs/home.h…

www.tutorialsteacher.com/typescript

github.com/dzharii/awe…

github.com/typescript-…


爲了達到學習和實踐 TypeScript 的目的,我用 TS 和 React-Native(用了 hooks)構建了一個簡單的 CurrencyConverter (匯率轉換) 程序。你能夠在 這裏 查看這個項目。

感謝、祝賀你閱讀到這裏!若是你對此有任何想法,請隨時發表評論。

相關文章
相關標籤/搜索