今年10月初尤雨溪在 GitHub 發佈了 vue3 的 Pre-Alpha 版本源碼,同時大部分源碼使用了 TypeScript 語言進行編寫。能夠說 TypeScript 已經成爲前端開發將來的趨勢。javascript
本篇大部份內容講 TypeScript 的基礎知識,後續內容會更新介紹 TypeScript 在工做中的項目開發及運用。若是您想要獲得最新的更新,能夠點擊下面的連接:html
TypeScript 是一種由微軟開發的自由和開源的編程語言,它是 JavaScript 的一個超集,擴展了 JavaScript 的語法。java
經過 npm 安裝webpack
$ npm install typescript -g
複製代碼
以上命令會在全局環境下安裝 tsc
和 tsserver
兩個命令,安裝完成以後,咱們就能夠在任何地方執行它了。git
TypeScript 獨立服務器(又名 tsserver )是一個節點可執行文件,它封裝了 TypeScript 編譯器和語言服務,並經過 JSON 協議公開它們。tsserver 很是適合編輯器和 IDE 支持。es6
通常工做中不經常使用到它。進一步瞭解tsservergithub
tsc 爲 typescript compiler 的縮寫,即 TypeScript 編譯器,用於將 TS 代碼編譯爲 JS 代碼。使用方法以下:web
$ tsc index.ts
複製代碼
編譯成功後,就會在相同目錄下生成一個同名 js 文件,你也能夠經過命令參數來修改默認的輸出名稱。
默認狀況下編譯器以 ECMAScript 3(ES3)爲目標。能夠經過 tsc -h
命令查看相關幫助,能夠了解更多的配置。
咱們約定使用 TypeScript 編寫的文件以 .ts
爲後綴,用 TypeScript 編寫 React 時,以 .tsx
爲後綴。
結合 tsc
命令,咱們一塊兒寫一個簡單的例子。
建立一個 index.ts 文件。
let text: string = 'Hello TypeScript'
複製代碼
執行 tsc index.ts
命令,會在同目錄下生成 index.js 文件。
var text = 'Hello TypeScript';
複製代碼
一個簡單的例子就實現完了。咱們能夠經過官網提供的 Playground 進行驗證。
可是在項目開發過程當中咱們會結合構建工具,如 webpack
,和對應的本地服務 dev-server
等相關工具一同使用。
接下來把咱們瞭解到的知識結合在一塊兒。搭建一個完整的項目。
項目根目錄中有一個 tsconfig.json
文件,簡單介紹其做用。
若是一個目錄下存在一個 tsconfig.json
文件,那麼它意味着這個目錄是 TypeScript 項目的根目錄。tsconfig.json
文件中指定了用來編譯這個項目的根文件和編譯選項。 一個項目能夠經過如下方式之一來編譯:
tsc
,編譯器會從當前目錄開始去查找 tsconfig.json文
件,逐級向上搜索父目錄。tsc
,且使用命令行參數 --project
(或 -p
)指定一個包含 tsconfig.json
文件的目錄。當命令行上指定了輸入文件時,tsconfig.json文件會被忽略。
TypeScript 支持與 JavaScript 幾乎相同的數據類型。
String、Number、Boolean、Object(Array、Function)、Symbol、undefined、null
void、any、never、元組、枚舉、高級類型
做用:至關於強類型語言中的類型聲明
語法:(變量/函數): type
咱們使用 string
表示文本數據類型。 和 JavaScript 同樣,可使用雙引號 "
或單引號 '
表示字符串, 反引號 ` 來定義多行文本和內嵌表達式。
let str: string = 'abc'
複製代碼
和 JavaScript 同樣,TypeScript 裏的全部數字都是浮點數。這些浮點數的類型是 number
。 除了支持十進制和十六進制字面量,TypeScript 還支持 ECMAScript 2015 中引入的二進制和八進制字面量。
let decLiteral: number = 6
let hexLiteral: number = 0xf00d
let binaryLiteral: number = 0b1010
let octalLiteral: number = 0o744
複製代碼
咱們使用 boolean
表示布爾類型,表示邏輯值 true
/ false
。
let bool: boolean = true
複製代碼
TypeScript 有兩種定義數組的方式。 第一種,能夠在元素類型後加上 []
。 第二種,可使用數組泛型 Array<元素類型>
。 此外,在元素類型中可使用聯合類型。 符號 |
表示或。
let arr1: number[] = [1, 2, 3]
let arr2: Array<number> = [1, 2, 3]
let arr3: Array<number | string> = [1, 2, 3, 'a']
複製代碼
元組類型用來表示已知元素數量和類型的數組,各元素的類型沒必要相同,對應位置的類型必須相同。
let tuple: [number, string] = [0, '1']
tuple = ['1', 0] // Error
複製代碼
當訪問一個已知索引的元素,會獲得正確的類型:
tuple[0].toFixed(2)
tuple[1].toFixed(2) // Error: Property 'toFixed' does not exist on type 'string'.
複製代碼
能夠調用數組 push
方法添加元素,但並不能讀取新添加的元素。
tuple.push('a')
console.log(tuple) // [0, "1", "a"]
tuple[2] // Error: Tuple type '[number, string]' of length '2' has no element at index '2'.
複製代碼
咱們使用 enum
表示枚舉類型。 枚舉成員值只讀,不可修改。 枚舉類型是對 JavaScript 標準數據類型的一個補充。C# 等其它語言同樣,使用枚舉類型爲一組數值賦予友好的命名。
初始值爲 0, 逐步遞增,也能夠自定義初始值,以後根據初始值逐步遞增。
enum Role {
Reporter = 1,
Developer,
Maintainer,
Owner,
Guest
}
console.log(Role.Developer) // 2
console.log(Role[2]) // Developer
複製代碼
數字枚舉會反向映射,能夠根據索引值反向得到枚舉類型。緣由以下編譯後代碼所示:
var Role;
(function (Role) {
Role[Role["Reporter"] = 1] = "Reporter";
Role[Role["Developer"] = 2] = "Developer";
Role[Role["Maintainer"] = 3] = "Maintainer";
Role[Role["Owner"] = 4] = "Owner";
Role[Role["Guest"] = 5] = "Guest";
})(Role || (Role = {}));
複製代碼
字符串枚舉不支持反向映射
enum Message {
Success = '成功',
Fail = '失敗'
}
複製代碼
在枚舉關鍵字前添加 const
,該常量枚舉會在編譯階段被移除。
const enum Month {
Jan,
Feb,
Mar
}
let month = [Month.Jan, Month.Feb, Month.Mar]
複製代碼
編譯後:
"use strict";
var month = [0 /* Jan */, 1 /* Feb */, 2 /* Mar */]; // [0 /* Jan */, 1 /* Feb */, 2 /* Mar */]
複製代碼
外部枚舉(Ambient Enums)是使用 declare enum
定義的枚舉類型。
declare enum Month {
Jan,
Feb,
Mar
}
let month = [Month.Jan, Month.Feb, Month.Mar]
複製代碼
編譯後:
"use strict";
let month = [Month.Jan, Month.Feb, Month.Mar];
複製代碼
declare
定義的類型只會用於編譯時的檢查,編譯結果中會被刪除。因此按照上述例子編譯後的結果來看,顯然是不能夠的。由於 Month 未定義。
declare
和 const
能夠同時存在TypeScript 有兩種定義對象的方式。 第一種,能夠在元素後加上 object
。 第二種,可使用 { key: 元素類型 }
形式。 一樣在元素類型中可使用聯合類型。注意第一種形式對象元素爲只讀。
let obj1: object = { x: 1, y: 2 }
obj1.x = 3 // Error: Property 'x' does not exist on type 'object'.
let obj2: { x: number, y: number } = { x: 1, y: 2 }
obj2.x = 3
複製代碼
symbol
類型的值是經過 Symbol 構造函數來建立
let s: symbol = Symbol()
複製代碼
null
表示對象值缺失,undefined
表示未定義的值。
let un: undefined = undefined
let nu: null = null
複製代碼
若其餘類型須要被賦值爲 null
或 undefined
時, 在 tsconfig.json 中將 scriptNullChecks 設置爲 false。或者 使用聯合類型。
用於標識方法返回值的類型,表示該方法沒有返回值。
function noReturn (): void {
console.log('No return value')
}
複製代碼
聲明爲 any
的變量能夠賦予任意類型的值。
let x: any
x = 1
x = 'a'
x = {}
let arr: any[] = [1, 'a', null]
複製代碼
咱們先回顧在 JavaScript 中,使用 es6 語法定義一個函數。
let add = (x, y) => x + y
複製代碼
上面例子中,add
函數有兩個參數 x
和 y
返回其相加之和。 該例子放在 TypeScript 中會提示 參數 x
和 y
隱含一個 any
類型。 因此咱們修改以下:
let add = (x: number, y: number): number => x + y
複製代碼
給參數添加 number
類型,在括號以後也添加返回值的類型。這裏返回值類型能夠省略,由於 TypeScript 有類型推斷機制,這個咱們以後詳細介紹。
接下來咱們使用 TypeScript 定義一個函數類型並實現它。
let plus: (x: number, y: number) => number
plus = (a, b) => a + b
plus(2, 2) // 2
複製代碼
never
類型表示的是那些永不存在的值的類型。 例如,never
類型是那些老是會拋出異常或根本就不會有返回值的函數表達式或箭頭函數表達式的返回值類型;變量也多是 never
類型,當它們被永不爲真的類型保護所約束時。
never
類型是任何類型的子類型,也能夠賦值給任何類型;然而,沒有類型是 never
的子類型或能夠賦值給 never
類型(除了 never
自己以外)。 即便 any
也不能夠賦值給 never
。
let error = (): never => {
throw new Error('error')
}
let endless = (): never => {
while(true) {}
}
複製代碼
any
在 TypeScript 中,咱們可使用接口 interface
來定義對象類型。
接口是一系列抽象方法的聲明,是一些方法特徵的集合,這些方法都應該是抽象的,須要由具體的類去實現,而後第三方就能夠經過這組抽象方法調用,讓具體的類執行具體的方法。
接下來,定義一個簡單的接口:
interface Person {
name: string
age: number
}
let man: Person = {
name: 'James',
age: 30
}
複製代碼
咱們定義了一個接口 Person
和變量 man
,變量的類型是 Person
。 這樣咱們就約束了該變量的值中對象的 key
和 value
要和接口一致。
須要注意的是:
接口的全部屬性可能都不是必需的。
interface Person {
name: string
age?: number
}
let man: Person = {
name: 'James'
}
複製代碼
屬性名前使用 readonly
關鍵字制定爲只讀屬性,初始化後不可更改。
interface Person {
readonly name: string
age: number
}
let man: Person = {
name: 'James',
age: 30
}
man.name = 'Tom' // Error: Cannot assign to 'name' because it is a read-only property.
複製代碼
用任意的字符串索引,使其能夠獲得任意的結果。
interface Person {
name: string
age: number
[x: string]: any
}
let man: Person = {
name: 'James',
age: 30,
height: '180cm'
}
複製代碼
除了 name
和 age
必須一致之外,其餘屬性能夠隨意定義數量不限。
interface Person {
name: string
age: number
[x: string]: string
}
let man: Person = {
name: 'James',
age: 30,
height: '180cm'
}
/** * Type '{ name: string; age: number; height: string; }' is not assignable to type 'Person'. * Property 'age' is incompatible with index signature. * Type 'number' is not assignable to type 'string'. */
複製代碼
能夠獲得任意長度的數組。
interface StringArray {
[i: number]: string
}
let chars: StringArray = ['a', 'b']
複製代碼
接口可以描述 JavaScript 中對象擁有的各類各樣的外形。 除了描述帶有屬性的普通對象外,接口也能夠描述對象類型和函數類型。
示例以下:
interface List {
readonly id: number
name: string
age?: number
}
interface Result {
data: List[]
}
function render (result: Result) {
console.log(JSON.stringify(result))
}
複製代碼
首先咱們定義了一個 List
對象接口,它的內部有 id
、name
和 age
屬性。接下來咱們又定義了一個對象接口,這個對象接口有隻一個屬性 data
,它類型爲 List[]
。接下來有一個函數,參數類型爲 Result
。
接下來咱們定義一個變量 result
,將它傳入 render
函數。
let result = {
data: [
{ id: 1, name: 'A', sex: 'male' },
{ id: 2, name: 'B' }
]
}
render(result)
複製代碼
這裏須要注意 data
數組內的第一個對象裏,增長了一個 sex
屬性,可是在上面的接口定義中沒有 sex
屬性。這時把對象賦給 result
變量,傳入函數,不會被編譯器檢查到。
再看下面的例子:
render({
data: [
{ id: 1, name: 'A', sex: 'male' },
{ id: 2, name: 'B' }
]
})
// Error: Object literal may only specify known properties, and 'sex' does not exist in type 'List'.
複製代碼
咱們將對象字面當作參數傳給了 render
函數時,編譯器會對對象內的屬性進行檢查。
咱們能夠經過類型斷言規避這個問題
render({
data: [
{ id: 1, name: 'A', sex: 'male'},
{ id: 2, name: 'B' }
]
} as Result)
複製代碼
除了使用 as
關鍵字,還能夠用 <>
符號:
render(<Result>{
data: [
{ id: 1, name: 'A', sex: 'male'},
{ id: 2, name: 'B' }
]
})
複製代碼
爲了使用接口表示函數類型,咱們須要給接口定義一個調用簽名。 它就像是一個只有參數列表和返回值類型的函數定義。參數列表裏的每一個參數都須要名字和類型。
在數據類型中咱們提到過,能夠用一個變量聲明一個函數類型。
let add: (x: number, y: number) => number
複製代碼
此外,咱們還能夠用接口來定義它。
interface Add {
(x: number, y: number): number
}
let add: Add = (a, b) => a + b
複製代碼
除此以外,還有一種更簡潔的方式就是使用類型別名
類型別名使用 type
關鍵字
type Add = (x: number, y: number) => number
let add: Add = (a, b) => a + b
複製代碼
interface
定義函數(Add)和用 type
定義函數(Add)有區別?type
和 interface
多數狀況下有相同的功能,就是定義類型。 但有一些小區別:
type:不是建立新的類型,只是爲一個給定的類型起一個名字。type還能夠進行聯合、交叉等操做,引用起來更簡潔。
interface:建立新的類型,接口之間還能夠繼承、聲明合併。建議優先使用 interface
。
和 JavaScript 同樣,TypeScript 函數能夠建立有名字的函數或匿名函數,TypeScript 爲 JavaScript 函數添加了額外的功能,讓咱們能夠更容易的使用它。
在基本類型和接口部分中多多少少提到過函數,接下來總結四種定義函數的方式。
function add (x: number, y: number) {
return x + y
}
const add: (x: number, y: number) => number
type add = (x: number, y: number) => number
interface add {
(x: number, y: number) => number
}
複製代碼
TypeScript 裏的每一個函數參數都是必要的。這裏不是指不能把 null
、undefined
當作參數,而是說編譯器檢查用戶是否爲每一個參數都傳入了值。也就是說,傳遞給一個函數的參數個數必須與函數指望的參數個數保持一致。咱們舉個例子:
function add (x: number, y: number, z: number) {
return x + y
}
add(1, 2) // Error: Expected 3 arguments, but got 2.
複製代碼
在上述例子中,函數定義了3個參數,分別爲 x
、y
、z
,結果返回 x
和 y
的和。並無使用參數 z
,調用 add
只傳入 x
和 y
的值。這時 TypeScript 檢查機制提示預期爲三個參數,但實際只傳入兩個參數的錯誤。如何避免這種狀況呢?
在 TypeScript 裏咱們能夠在參數名旁使用 ?
實現可選參數的功能。
function add (x: number, y: number, z?: number) {
return x + y
}
add(1, 2)
複製代碼
通過修改,參數 z
變爲可選參數,檢查經過。
與 JavaScript 相同,在 TypeScript 裏函數參數一樣能夠設置默認值,用法一致。
function add (x: number, y = 2) {
return x + y
}
複製代碼
根據類型推斷機制,參數 y
爲推斷爲 number
類型。
與 JavaScript 相同。TypeScript 能夠把全部參數收集到一個變量裏。
function add (x: number, ...rest: number[]) {
return x + rest.reduce((prev, curr) => prev + curr)
}
add(1, 2, 3) // 6
複製代碼
TypeScript 的函數重載,要求咱們先定義一系列名稱相同的函數聲明。
function add (...rest: number[]): number
function add (...rest: string[]): string
function add (...rest: any[]): any {
let first = rest[0]
let type = typeof first
switch (type) {
case 'number':
return rest.reduce((prev, curr) => prev + curr)
case 'string':
return rest.join('')
}
return null
}
複製代碼
上面例子中,咱們定義了三個相同名稱的函數,參數分別爲 number
、string
、any
類型數組,相繼返回的類型與參數類型相同。當調用該函數時,TypeScript 編譯器可以選擇正確的類型檢查。在重載列表裏,會從第一個函數開始檢查,從上而下,因此咱們使用函數重載時,應該把最容易用到的類型放在最上面。
any
類型函數不是重載列表的一部分傳統的 JavaScript 使用函數和基於原型的繼承來建立可重用的組件。
function Point (x, y) {
this.x = x
this.y = y
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')'
}
var p = new Point(1, 2)
複製代碼
從 ES6 開始,咱們可以使用基於類的面向對象的方式。
class Point {
constructor (x, y) {
this.x = x
this.y = y
}
toString () {
return `(${this.x}, ${this.y})`
}
}
複製代碼
TypeScript 除了保留了 ES6 中類的功能之外,還增添了一些新的功能。
class Dog {
constructor (name: string) {
this.name = name
}
name: string
run () {}
}
class Husky extends Dog {
constructor (name: string, color: string) {
super(name)
this.color = color
}
color: string
}
複製代碼
上面的例子中須要注意如下幾點:
this
的屬性以前,必定要調用 super
方法;Dog.prototype
=> {constructor: ƒ, run: ƒ}
,new Dog('huang')
=> {name: "huang"}
TypeScript 可使用三種訪問修飾符(Access Modifiers),分別是 public
、private
和 protected
public
修飾的屬性或方法是公有的,能夠在任何地方被訪問到,默認全部的屬性和方法都是 public
private
修飾的屬性或方法是私有的,不能在聲明它的類的外部訪問,包括繼承它的類也不能夠訪問
protected
修飾的屬性或方法是受保護的,它和 private
相似,區別是它在子類中也是容許被訪問
以上三種能夠修飾構造函數,默認爲 public
,當構造函數爲 private
時,該類不容許被繼承或實例化;當構造函數爲 protected
時,該類只容許被繼承。
readonly
修飾的屬性爲只讀屬性,只容許出如今屬性聲明或索引簽名中。
公共修飾符
class Animal {
public name: string
public constructor (name: string) {
this.name = name
}
}
let a = new Animal('Jack')
console.log(a.name) // Jack
a.name = 'Tom'
console.log(a.name) // Tom
複製代碼
私有修飾符
class Animal {
private name: string
public constructor (name: string) {
this.name = name
}
}
let a = new Animal('Jack')
console.log(a.name) // Error: Property 'name' is private and only accessible within class 'Animal'.
class Cat extends Animal {
constructor (name: string) {
super(name)
console.log(this.name) // Error: // Property 'name' is private and only accessible within class 'Animal'.
}
}
複製代碼
須要注意的是,TypeScript 編譯以後的代碼中,並無限制 private
屬性在外部的可訪問性。
上面的例子編譯後的代碼以下:
var Animal = (function () {
function Animal (name) {
this.name = name
}
return Animal
}())
var a = new Animal('Jack')
console.log(a.name)
複製代碼
受保護修飾符
class Animal {
protected name: string
public constructor (name: string) {
this.name = name
}
}
class Cat extends Animal {
constructor (name: string) {
super(name)
console.log(this.name)
}
}
複製代碼
class Animal {
// public name: string
constructor (public name: string) {
this.name = name
}
}
class Cat extends Animal {
constructor (public name: string) {
super(name)
}
}
let a = new Animal('Jack')
console.log(a.name) // Jack
a.name = 'Tom'
console.log(a.name) // Tom
複製代碼
只讀修飾符
class Animal {
readonly name: string
public constructor (name: string) {
this.name = name
}
}
let a = new Animal('Jack')
console.log(a.name) // Jack
a.name = 'Tom' //Error: Cannot assign to 'name' because it is a read-only property.
複製代碼
注意若是 readonly
和其餘訪問修飾符同時存在的話,須要寫在其後面。
class Animal {
// public readonly name: string
public constructor (public readonly name: string) {
this.name = name
}
}
複製代碼
abstract
用於定義抽象類和其中的抽象方法。須要注意如下兩點:
抽象類不容許被實例化
abstract class Animal {
public name: string
public constructor (name: string) {
this.name = name
}
}
var a = new Animal('Jack') //Error: Cannot create an instance of an abstract class.
複製代碼
抽象類中的抽象方法必須被繼承類實現
abstract class Animal {
public name: string;
public constructor (name: string) {
this.name = name;
}
abstract sayHi (): any
}
class Cat extends Animal {
public color: string
sayHi () { console.log(`Hi`) }
constructor (name: string, color: string) {
super(name)
this.color = color
}
}
var a = new Cat('Tom', 'Blue')
複製代碼
本章節主要介紹類與接口之間實現、相互繼承的操做。
實現(implements)是面向對象中的一個重要概念。通常來說,一個類只能繼承自另外一個類,有時候不一樣類之間能夠有一些共有的特性,這時候就能夠把特性提取成接口(interface),用 implements
關鍵字來實現。這個特性大大提升了面向對象的靈活性。
interface Animal {
name: string
eat (): void
}
class Cat implements Animal {
constructor (name: string) {
this.name = name
}
name: string
eat () {}
}
複製代碼
interface Animal {
name: string
eat (): void
}
class Cat implements Animal {
constructor (name: string) {
this.name = name
}
name: string
// eat () {}
}
// Error: Class 'Cat' incorrectly implements interface 'Animal'. Property 'eat' is missing in type 'Cat' but required in type 'Animal'.
複製代碼
private
或 protected
。interface Animal {
name: string
eat (): void
}
class Cat implements Animal {
constructor (name: string) {
this.name = name
}
private name: string
eat () {}
}
// Error: Class 'Cat' incorrectly implements interface 'Animal'. Property 'name' is private in type 'Cat' but not in type 'Animal'.
複製代碼
interface Animal {
new (name: string): void
name: string
eat (): void
}
class Cat implements Animal {
constructor (name: string) {
this.name = name
}
name: string
eat () {}
}
// Error: Class 'Cat' incorrectly implements interface 'Animal'. Type 'Cat' provides no match for the signature 'new (name: string): void'.
複製代碼
實現方法以下:
interface Animal {
name: string
eat (): void
}
interface Predators extends Animal {
run (): void
}
class Cat implements Predators {
constructor (name: string) {
this.name = name
}
name: string
eat () {}
run () {}
}
複製代碼
,
分割,同理實現多個接口方式相同。interface Animal {
name: string
eat (): void
}
interface Lovely {
cute: number
}
interface Predators extends Animal, Lovely {
run (): void
}
class Cat implements Predators {
constructor (name: string, cute: number) {
this.name = name
this.cute = cute
}
name: string
cute: number
eat () {}
run () {}
}
複製代碼
實現方法以下:
class Auto {
constructor (state: string) {
this.state = state
}
state: string
}
interface AutoInterface extends Auto {}
class C implements AutoInterface {
state = ''
}
複製代碼
interface SearchFunc {
(source: string, subString: string): boolean
}
let mySearch: SearchFunc
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1
}
複製代碼
一個函數還能夠有本身的屬性和方法
interface Counter {
(start: number): string
interval: number
reset (): void
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) {}
counter.interval = 123
counter.reset = function () {}
return counter
}
let c = getCounter()
c(10)
c.reset()
c.interval = 5.0
複製代碼
泛型(Generics)是指在定義函數、接口或類的時候,不預先指定具體的類型,而在使用的時候再指定類型的一種特性。
首先,咱們來實現一個函數 createArray
,它能夠建立一個指定長度的數組,同時將每一項都填充一個默認值:
function createArray(length: number, value: any): Array<any> {
let result = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
createArray(3, 'x') // ['x', 'x', 'x']
複製代碼
這段代碼編譯不會報錯,可是一個顯而易見的缺陷是,它並無準確的定義返回值的類型:
Array<any>
容許數組的每一項都爲任意類型。可是咱們預期的是,數組中每一項都應該是輸入的 value
的類型。
這時候,泛型就派上用場了:
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
createArray<string>(3, 'x') // ['x', 'x', 'x']
複製代碼
上例中,咱們在函數名後添加了 <T>
,其中 T
用來指代任意輸入的類型,在後面的輸入 value: T
和輸出 Array<T>
中便可使用了。
接着在調用的時候,能夠指定它具體的類型爲 string
。固然,也能夠不手動指定,而讓類型推斷自動推算出來:
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
createArray(3, 'x') // ['x', 'x', 'x']
複製代碼
一樣類型數組也能夠被類型推斷。
function log<T> (value: T): T {
console.log(value)
return value
}
log<string[]>(['a', 'b'])
// or
log(['a', 'b'])
複製代碼
定義泛型的時候,能夠一次定義多個類型參數:
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]]
}
swap([7, 'seven']) // ['seven', 7]
複製代碼
上例中,咱們定義了一個 swap 函數,用來交換輸入的元組。
在函數內部使用泛型變量的時候,因爲事先不知道它是哪一種類型,因此不能隨意的操做它的屬性或方法。
function loggingIdentity<T>(arg: T): T {
console.log(arg.length) // Error: Property 'length' does not exist on type 'T'.
return arg
}
複製代碼
上例中,泛型 T
不必定包含 length
屬性,因此編譯的時候會報錯。
這時,咱們能夠對泛型進行約束,只容許這個函數傳入那些包含 length
屬性的變量。這就叫泛型約束。
interface Lengthwise {
length: number
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length)
return arg
}
複製代碼
上例中,咱們使用了 extends
約束了泛型 T
必須符合接口 Lengthwise
的形狀,也就是必須包含 length
屬性。
此時若是調用 loggingIdentity
函數的時候,傳入的參數不包含 length
,那麼在編譯階段就會報錯了。
interface Lengthwise {
length: number
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length)
return arg
}
loggingIdentity(7) // Error: Argument of type '7' is not assignable to parameter of type 'Lengthwise'.
複製代碼
多個類型參數之間也能夠相互約束。
function copyFields<T extends U, U>(target: T, source: U): T {
for (let id in source) {
target[id] = (<T>source)[id]
}
return target
}
let x = { a: 1, b: 2, c: 3, d: 4 }
copyFields(x, { b: 10, d: 20 }) // { a: 1, b: 10, c: 3, d: 20 }
複製代碼
上述例子中,咱們使用了兩個類型參數,其中要求 T
繼承 U
,這樣就保證了 U
上不會出現 T
中不存在的字段。
能夠用泛型來約束函數的參數和返回值類型。
type Log = <T>(value: T) => T
let log: Log = (value) => {
console.log(value)
return value
}
log<number>(2) // 2
log('2') // '2'
log(true) // <boolean>true
複製代碼
以前學習過,可使用接口的方式來定義一個函數須要符合的形狀。
interface SearchFunc {
(source: string, subString: string): boolean
}
let mySearch: SearchFunc
mySearch = function (source: string, subString: string) {
return source.search(subString) !== -1
}
複製代碼
一樣也可使用含有泛型的接口來定義函數的形狀。
interface CreateArrayFunc {
<T>(length: number, value: T): Array<T>
}
let createArray: CreateArrayFunc
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
createArray(3, 'x') // ['x', 'x', 'x']
複製代碼
進一步,咱們能夠把泛型參數提早到接口名上。
interface CreateArrayFunc<T> {
<T>(length: number, value: T): Array<T>
}
let createArray: CreateArrayFunc<any>
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
createArray(3, 'x') // ['x', 'x', 'x']
複製代碼
注意,此時在使用泛型接口的時候,須要定義泛型的類型。
若不想在使用泛型接口時定義泛型的類型,那麼,須要在接口名上的泛型參數設置默認類型。
interface CreateArrayFunc<T = any> {
<T>(length: number, value: T): Array<T>
}
let createArray: CreateArrayFunc
複製代碼
與泛型接口相似,泛型也能夠用於類的類型定義中。
class Log<T> {
run (value: T) {
console.log(value)
return value
}
}
let log1 = new Log<number>()
log1.run(1) // 1
let log2 = new Log()
log2.run('1') // '1'
複製代碼
class Log<T> {
static run (value: T) {
console.log(value)
return value
}
}
// Error: Static members cannot reference class type parameters.
複製代碼
TypeScript 編譯器在作類型檢查時,所秉承的一些原則,以及表現出的一些行爲。
本章節分爲三大部分:類型推斷、類型兼容性、類型保護。
不須要指定變化的類型(函數的返回值類型),TypeScript 能夠根據某些規則自動爲其推斷出一個類型。
基本類型推斷常常出如今初始化變量的時候。
let a
// let a: any
let a = 1
// let a: number
let a = []
// let a: any[]
複製代碼
聲明變量 a
時,咱們不指定它的類型,ts
就會默認推斷出它是 any
類型。
若是咱們將它複製爲 1
,ts
就會推斷出它是 number
類型。
若是咱們將它複製爲 []
,ts
就會推斷出它是 any
類型的數組。
基本類型推斷還會出如今定義函數參數。
let a = (x = 1) => {}
// let a: (x?: number) => void
複製代碼
聲明函數 a
,設置一個參數 x
,爲它賦值一個默認參數 1
,此時 ts
就會推斷出它是 number
類型。一樣返回值類型也會被推斷。
當須要從多個類型中推斷出一個類型時,ts
就會盡量的推斷出一個最佳通用類型。
let a = [1, null]
// let a: (number | null)[]
複製代碼
聲明一個變量 a
,值爲一個包含數字 1
和 null
的數組。此時,變量 a
就被推斷爲 number
和 null
的聯合類型。
以上的類型推斷都是從右向左的推斷,根據表達式的值推斷出變量的類型。還有一種方式是從左到右,根據上下文推斷。
一般發生在事件處理中。
window.onkeydown = (event) => {
}
// (parameter) event: KeyboardEvent
複製代碼
爲 window
綁定 onkeydown
事件,參數爲 event
,此時 ts
會根據左側的事件綁定推斷出右側事件的類型。
當一個類型 Y 能夠賦值給另外一個類型 X 時,咱們能夠認爲類型 X 兼容類型 Y。
X 兼容 Y : X (目標類型) = Y (源類型)
let s: string = 'abc'
s = null
複製代碼
默認會提示 Type 'null' is not assignable to type 'string'. 若是將 tsconfig.json
內的 strictNullChecks
的值設置爲 false
,這時編譯就不會報錯。
能夠說明 string
類型兼容 null
類型,null
是 string
類型的子類型。
示例以下:
interface X {
a: any
b: any
}
interface Y {
a: any
b: any
c: any
}
let x: X = { a: 1, b: 2 }
let y: Y = { a: 1, b: 2, c: 3 }
x = y
y = x // Error: Property 'c' is missing in type 'X' but required in type 'Y'.
複製代碼
y
能夠賦值給 x
,x
不能夠賦值給 y
。
示例以下:
type Handler = (a: number, b: number) => void
function hof(handler: Handler) {
return handler
}
let handler1 = (a: number) => {}
hof(handler1)
let handler2 = (a: number, b: number, c: number) => {}
hof(handler2)
// Error: Argument of type '(a: number, b: number, c: number) => void' is not assignable to parameter of type 'Handler'.
let handler3 = (a: string) => {}
hof(handler3)
// Error: Types of parameters 'a' and 'a' are incompatible. Type 'number' is not assignable to type 'string'.
複製代碼
上述示例中,目標類型 handler
有兩個參數,定義了三個不一樣的函數進行測試。
handler1
函數只有一個參數,將 handler1
傳入 hof
方法做爲參數(兼容)handler2
函數有三個參數,一樣做爲參數傳入 hof
方法(不兼容)。handler2
函數參數類型與目標函數參數類型不一樣(不兼容)示例以下:
// 固定參數
let a = (p1: number, p2: number) => {}
// 可選參數
let b = (p1?: number, p2?: number) => {}
// 剩餘參數
let c = (...args: number[]) => {}
a = b
a = c
b = a // Error
b = c // Error
c = a
c = b
複製代碼
tsconfig.json
內的 strictFunctionTypes
的值設置爲 false
,這時編譯就不會報錯。剩餘參數兼容固定參數和可選參數。示例以下:
interface Point3D {
x: number
y: number
z: number
}
interface Point2D {
x: number
y: number
}
let p3d = (point: Point3D) => {}
let p2d = (point: Point2D) => {}
p3d = p2d
p2d = p3d // Error: Property 'z' is missing in type 'Point2D' but required in type 'Point3D'.
複製代碼
若是想要上述示例中的 p2d = p3d 兼容。將 tsconfig.json
內的 strictFunctionTypes
的值設置爲 false
。
示例以下:
let f = () => ({ name: 'Alice' })
let g = () => ({ name: 'Alice', location: 'Beijing' })
f = g
g = f // Error
複製代碼
在函數部分中有介紹函數重載,這裏咱們重溫一下。
function overload (a: number, b: number): number function overload (a: string, b: string): string function overload (a: any, b: any): any {} 複製代碼
函數重載分爲兩個部分,第一個部分爲函數重載的列表,也就是第1、二個 overload
函數,也就是目標函數。第二個部分就是函數的具體實現,也就是第三個 overload
函數,也就是源函數。
示例以下:
enum Fruit { Apple, Banana }
enum Color { Red, Yellow }
let fruit: Fruit.Apple = 3
let no: number = Fruit.Apple
let color: Color.Red = Fruit.Apple // Error
複製代碼
示例以下:
class A {
constructor (p: number, q: number) {}
id: number = 1
}
class B {
static s = 1
constructor (p: number) {}
id: number = 2
}
let aa = new A(1, 2)
let bb = new B(1)
aa = bb
bb = aa
複製代碼
示例以下:
interface Empty<T> {}
let obj1: Empty<number> = {}
let obj2: Empty<String> = {}
obj1 = obj2
// 設置屬性
interface Empty<T> {
value: T
}
let obj1: Empty<number> = { value: 1 }
let obj2: Empty<String> = { value: 'a'}
obj1 = obj2 // Error
複製代碼
obj1
與 obj2
相互兼容,若此時 Empty
設置了屬性 value: T
時,obj1
與 obj2
不兼容。泛型函數
let log1 = <T>(x: T): T => {
console.log('x')
return x
}
let log2 = <U>(y: U): U => {
console.log('y')
return y
}
log1 = log2
複製代碼
TypeScript
可以在特定的區塊中保證變量屬於某種肯定的類型。
能夠再此區塊中放心地引用此類型的屬性,或者調用此類型的方法。
enum Type { Strong, Week }
class Java {
helloJava () {
console.log('hello java')
}
java: any
}
class JavaScript {
helloJavaScript () {
console.log('hellp javascript')
}
javascript: any
}
function getLanguage (type: Type, x: string | number) {
let lang = type === Type.Strong ? new Java() : new JavaScript()
if (lang.helloJava) {
lang.helloJava()
} else {
lang.helloJavaScript()
}
return lang
}
getLanguage(Type.Strong)
複製代碼
定義 getLanuage
函數參數 type
,判斷 type
爲強類型時,返回 Java
實例,反之返回 JavaScript
實例。
判斷 lang
是否有 helloJava
方法,有則執行該方法,反之執行 JavaScript
方法。此時這裏有一個錯誤 Property 'helloJava' does not exist on type 'Java | JavaScript'.
。
解決這個錯誤,咱們須要給 lang
添加類型斷言。
if ((lang as Java).helloJava) {
(lang as Java).helloJava()
} else {
(lang as JavaScript).helloJavaScript()
}
複製代碼
這顯然不是很是理想的解決方案,代碼可讀性不好。咱們能夠利用類型保護機制,以下幾個方法。
判斷實例是否屬於某個類
if (lang instanceof Java) {
lang.helloJava()
} else {
lang.helloJavaScript()
}
複製代碼
判斷一個屬性是否屬於某個對象
if ('java' in lang) {
lang.helloJava()
} else {
lang.helloJavaScript()
}
複製代碼
判斷一個基本類型
if (typeof x === 'string') {
x.length
} else {
x.toFixed(2)
}
複製代碼
function isJava(lang: Java | JavaScript): lang is Java {
return (lang as Java).helloJava !== undefined
}
if (isJava(lang)) {
lang.helloJava()
} else {
lang.helloJavaScript()
}
複製代碼
介紹五種 TypeScript 高級類型:交叉類型、聯合類型、索引類型、映射類型、條件類型。
這些類型在前面多多少少有被提到過,咱們在統一梳理一遍。
&
符號,多個類型合併爲一個類型,新的類型具備全部類型的特性。
interface DogInterface {
run (): void
}
interface CatInterface {
jump (): void
}
let pet: DogInterface & CatInterface = {
run () {},
jump () {}
}
複製代碼
取值能夠爲多種類型中的一種
let a: number | string = 1 // or '1'
複製代碼
字面量聯合類型
let a: 'a' | 'b' | 'c'
let b: 1 | 2 | 3
複製代碼
對象聯合類型
interface DogInterface {
run (): void
}
interface CatInterface {
jump (): void
}
class Dog implements DogInterface {
run () {}
eat () {}
}
class Cat implements CatInterface {
jump () {}
eat () {}
}
enum Master { Boy, Girl }
function getPet (master: Master) {
let pet = master === Master.Boy ? new Dog() : new Cat()
pet.eat()
return pet
}
複製代碼
getPet
方法體內的 pet
變量被推斷爲 Dog
和 Cat
的聯合類型。在類型未肯定的狀況下,只能訪問聯合類型的公有成員 eat
方法。
let obj = {
a: 1,
b: 2,
c: 3
}
function getValues (obj: any, keys: string[]) {
return keys.map(key => obj[key])
}
getValues(obj, ['a', 'b']) // [1, 2]
getValues(obj, ['d', 'e']) // [undefined, undefined]
複製代碼
當 keys
傳入非 obj
中的屬性時,會返回 undefined
。如何進行約束呢?這裏就須要索引類型。
索引類型的查詢操做符 keyof T
表示類型 T 的全部公共屬性的字面量聯合類型
interface Obj {
a: number
b: string
}
let key: keyof Obj // let key: "a" | "b"
複製代碼
索引訪問操做符 T[K]
對象 T 的屬性 K 表明的類型
let value: Obj['a'] // let value: number
複製代碼
泛型約束 T extends U
let obj = {
a: 1,
b: 2,
c: 3
}
function getValues <T, U extends keyof T>(obj: T, keys: U[]): T[U][] {
return keys.map(key => obj[key])
}
getValues(obj, ['a', 'b']) // [1, 2]
getValues(obj, ['d', 'e']) // Type 'string' is not assignable to type '"a" | "b" | "c"'.
複製代碼
能夠講一箇舊的類型生成一個新的類型,好比把一個類型中的全部屬性設置成只讀。
interface Obj {
a: string
b: number
c: boolean
}
// 接口全部屬性設置成只讀
type ReadonlyObj = Readonly<Obj>
// 源碼
/** * Make all properties in T readonly */
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// 接口全部屬性設置成可選
type PartialObj = Partial<Obj>
// 源碼
/** * Make all properties in T optional */
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 抽取Obj子集
type PickObj = Pick<Obj, 'a' | 'b'>
// 源碼
/** * From T, pick a set of properties whose keys are in the union K */
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
type RecordObj = Record<'x' | 'y' , Obj>
複製代碼
ts
還有更多內置的映射類型,路徑在 typescript/lib/lib.es5.d.ts
內提供參考。
形式爲 T extends U ? X : Y
,若是類型 T
能夠賦值爲 U
結果就爲 X
反之爲 Y
。
type TypeName<T> =
T extends string ? 'string' :
T extends number ? 'number' :
T extends boolean ? 'boolean' :
T extends undefined ? 'undefined' :
T extends Function ? 'function' :
'object'
type T1 = TypeName<string> // type T1 = "string"
type T2 = TypeName<string[]> // type T2 = "object"
複製代碼
若 (A | B) extends U ? X : Y
形式,其約等於 (A extends U ? X : Y) | (B extends U ? X : Y)
type T3 = TypeName<string | number> // type T3 = "string" | "number"
複製代碼
利用該特性可實現類型過濾。
type Diff<T, U> = T extends U ? never : T
type T4 = Diff<'a' | 'b', 'a'> // type T4 = "b"
// 拆解
// Diff<'a', 'a'> | Diff<'b', 'a'>
// never | 'b'
// 'b'
複製代碼
根據 Diff
再作拓展。
type NotNull<T> = Diff<T, undefined | null>
type T5 = NotNull<string | number | undefined | null> // type T5 = string | number
複製代碼
以上 Diff
和 NotNull
條件類型官方已經實現了。
Exclude<T, U>
等於 Diff<T, U>
NonNullable<T>
等於 NotNull<T>
還有更多的官方提供的條件類型,可供你們參考。
// Extract<T, U>
type T6 = Extract<'a', 'a' | 'b'> // type T6 = "a"
// ReturnType<T>
type T7 = ReturnType<() => string> // type T7 = string
複製代碼