「學習筆記」TypeScript

前言

爲了趕在vue3.0正式版本發佈前,乘着倉庫中的代碼體積尚未開始膨脹,抓緊從新學一波typescript,方便閱讀vue新版本的源碼。第一次學習typescript是在2017年我剛開始接觸前端工做的時候,那時只是大體看了一些相關博客。第二次學習typescript是在2018年,工做的第二個年頭,對ts的理解和使用,也只僅限於例如類型檢查等基礎功能的使用,對於ts中一些高級概念一無所知。此次學習,是準備對ts進行一次系統性的學習和總結。順便提一嘴,typescript中文網上的部分文檔已是過期,英文好的同窗請直接閱讀ts的官網。對於ts中文網上過期的內容,我會使用emoji"👻"表情進行標記。本文是我在學習ts時,記錄下的筆記。javascript

基礎類型

// 布爾
const isCheck: boolean = false
// 數字
const year: number = 2077
// 字符串
const name: string = 'Cyberpunk'
// 數組
const list: number[] = [1, 2, 3]
const list: Array<number> = [1, 2, 3]
複製代碼

Tuple

元組Tuple,表示一個已知元素數量和類型的數組。訪問跨界元素時,ts會拋出錯誤。前端

👻:typescript中文網中的內容是,訪問跨界元素,使用聯合類型。是過期的內容。vue

const tuple: [number, string] = [2077, 'Cyberpunk']
複製代碼

Enum

能夠爲一組數據提供更語義化的名稱java

enum language {CPP = 1, JS = 2, JAVA = 3}
const lan: language = language.CPP // 1
// Enum類型,也能夠經過枚舉的值,獲取枚舉的名稱
const lanName: string = language[2] // 2
複製代碼

Any

Any爲不清楚類型的變量指定類型,變量將不會進行類型檢查。node

Void

Void表示沒有任何類型。若是一個變量爲Void類型,只能賦予undefined或者null。python

Null & Undefined

null, undefined是全部類型的子類型。null和undefined能夠賦值給number或者string類型。ios

若是tsconfig.json的配置項strictNullChecks設置爲true,那麼null, undefined只能賦予它們各自的類型以及Void類型。c++

Never

Never表示永遠不存在的類型。好比一個函數老是拋出錯誤,而沒有返回值。或者一個函數內部有死循環,永遠不會有返回值。函數的返回值就是Never類型。git

Object

object表示非原始類型,也就是除number,string,boolean,symbol,null或undefined以外的類型。好比一個函數接受一個對象做爲參數,可是參數上的key和value是不肯定的,此時參數可使用object類型。github

類型斷言

類型斷言告訴編譯器,咱們本身確切的知道變量的類型,而不須要進行類型檢查。

// as語法或是尖括號語法
alert((temp as string).length)
alert((<string>temp).length)
複製代碼

雙重斷言

S as T,只有當ST的子集或者TS的子集時,斷言才能成功

window.onclick = function (event: Event) {
  let mouseEvent = event as HTMLElement // error,Event,HTMLElement類型不是父子集關係
  mouseEvent = (event as any) as HTMLElement // ok
}
複製代碼

接口

接口的主要做用是對類型進行命名。類型則會對值進行結構檢查。在ts中不會說,「這個對象實現了這個接口」,ts中只須要關注值的外形,只要對象知足接口的值的外形,那麼它就是被容許的。

interface Bar { name: string }
interface Foo { name: string }

const bar: Bar = { name: 'Geralt' }
const foo: Foo = { name: 'V' }

function getName (hero: Bar) { return hero.name }
// hero接受Bar類型,可是Foo類型和Bar類型結構是一致的,因此使用Foo類型也是被容許的
getName(bar) // Geralt
getName(foo) // V
複製代碼

可選屬性

可選屬性是在接口定義的屬性後添加一個?,表示對象中的一些屬性只在某些條件下存在,或者根本不存在。

只讀屬性

只讀屬性是在接口定義的屬性前添加readonly關鍵字。只讀屬性一旦建立,就不能被修改了。

只讀數組

使用ReadonlyArray關鍵字能夠聲明一個只讀數組。可是咱們能夠經過類型斷言,繞過編譯器修改只讀數組。

let list: ReadonlyArray<number> = [1, 2, 3]
// error
list.push(4)
// ok
(list as number[]).push(4)
複製代碼

額外的屬性檢查

對象字面量會通過額外的屬性檢查,若是對象字面量存在目標類型中不存在的屬性,編譯器會拋出一個錯誤。

interface Bar { name: string, age?: number }
function getName (hero: Bar) { return hero.name }

// error,對象字面量會進行額外的屬性檢查
getName({ name: 'Geralt', gender: 'man' })
// ok,使用類型斷言會繞過檢查
getName({ name: 'Geralt', gender: 'man' } as Bar)
// ok,使用一個變量,變量是不會進行額外的屬性檢查
const bar = { name: 'Geralt', gender: 'man' }
getName(bar)
// 或者修改接口的定義,對於未知的屬性,使用索引簽名
複製代碼

函數類型

接口一樣能夠描述函數。實現接口的函數的參數名,不須要和接口定義的參數名相匹配。

interface FooFunc {
  (a: number, b: string): string
}
const foo: FooFunc = (a: number, b: string): string => a + b
// 若是不指定類型,TypeScript會自動對推斷參數以及返回值的類型
const far: FooFunc = (a, b) => a + b
複製代碼

可索引的類型

可索引類型具備一個索引簽名,它描述了對象索引的類型,還有相應的索引返回值類型。TypeScript支持兩種索引類型,字符串和數字。代碼中能夠同時使用兩種索引類型,可是數字索引的返回值的類型,必須是字符串索引的子類型(子集)。由於Foo['1']和Foo[1]是等同的。

interface Foo {
  [key: number]: string,
  [key: string]: string
}

interface NumberDictionary {
  [index: string]: number;
  length: number;    // ok
  name: string;      // error
}
interface Foo {
  [key: string]: number | string;
  length: number;    // ok
  name: string;      // ok
}
複製代碼

可索引的類型的嵌套

簡單的看一個例子,下面的可索引類型是不安全的

interface Foo {
  color?: string
  [key: string]: string
}
// 由於筆誤,寫錯了拼寫,color -> colour,可是因爲可索引類型的緣由,這並不會拋出錯誤
let a: Foo = {
  colour: 'blue'
}
複製代碼

合理的思路,是應該將可索引的類型,分離到單獨的屬性裏

interface Foo {
  color?: string
  other?: {
    [key: string]: string
  }
}
// error編譯器會拋出錯誤
let a: Foo = {
  colour: 'blue'
}
複製代碼

類類型

TypeScript可使用接口強制一個類的實例部分實現某種屬性或者方法。可是對於靜態部分,好比靜態屬性,靜態方法,constructor則不在檢查的範圍內。

interface WitcherClass {
  name: string; // 接口能夠定義`實例`的屬性
  getName(): string; // 接口能夠定義`實例`的方法
}

// Witcher類實現WitcherClass接口
class Witcher implements WitcherClass {
  name: string;
  getName () { return this.name }
  constructor () {}
}
複製代碼

繼承接口

接口之間可使用extends關鍵字實現繼承。一個接口能夠同時繼承多個接口,實現合成接口。

interface Witcher { name: string; }

interface Wolf extends Witcher { fencing: string }

const geralt: Wolf = {
  name: 'geralt',
  fencing: '拜年劍法'
}
複製代碼

接口繼承類

接口能夠繼承一個類,可是接口只會繼承類的定義,但不包括類的具體實現。接口會繼承類的privateprotected成員的修飾符。 當一個接口,繼承了包含了privateprotected成員的類時,該接口只能被該類和該類的子類所實現。

class Foo { protected name: string }
interface InterfaceFoo extends Foo { getName(): string }
// ok
class Bar extends Foo implements InterfaceFoo {
  getName() { return this.name }
}
// error, Far沒有name屬性,只有Foo以及Foo的子類纔有
class Far implements InterfaceFoo {
  getName() { return this.name }
}
複製代碼

修飾符

  • public 默認修飾符,TypeScript中類中的成員默認爲public
  • private 類的成員不能在類的外部訪問,子類也不能夠
  • protected 類的成員不能在類的外部訪問,可是子類中能夠訪問。若是一個類的構造函數,修飾符爲protected,那麼此類只能被繼承,沒法實例化。
  • readonly 關鍵字readonly能夠將實例的屬性,設置爲只讀

參數屬性

在構造函數的參數中使用privateprotectedpublic或者readonly,能夠將聲明和賦值合併至一處

// Boo,Far聲明name屬性的方式是一致的
class Boo {
  public name: string
  constructor (theName: string) {
    this.name = theName
  }
}
class Far {
  constructor (public name: string) {
  }
}
複製代碼

getters/setters

可使用getters/setters控制對類成員的讀寫,可是若是當類的成員只帶有get時,成員默認是隻讀的。

class Boo {
  // 控制_name的getters/setters行爲
  private _name: string
  get name(): string {
    return this._name
  }
  set name (newName): void {
    if (newName) {
      this._name = newName
    }
  }
}
複製代碼

靜態屬性/靜態方法

使用static關鍵詞,定義類的靜態屬性或者靜態方法,直接使用類名.,訪問靜態屬性或者靜態方法。

class Boo {
  static lan: string = 'JAVA' 
  static getLan () {
    return Boo.lan
  }
}
// JAVA
console.log(Boo.getLan())
複製代碼

抽象類

使用abstract關鍵詞定義抽象類。抽象類只能被繼承,不會被直接實例化。在抽象類中還可使用abstract定義抽象方法,抽象方法不包含具體的實現,必須在子類中實現抽象方法。

abstract class Boo {
  public name: string = 'boo'
  public abstract getName(): string
}
class Bar extends Boo {
  // 子類必須實現抽象類的抽象方法
  getName () {
    return this.name
  }
}
複製代碼

typeof

在TypeScript中聲明一個類的時候,聲明的實例的類型。而使用typeof 類名取的是類自己的類型

class Bar {
  static lan: string = 'Java'
  constructor (public age: number) {}
}
const a: Bar = new Bar(20)
// typeof 類名,獲取的是類自己的類型,包含了靜態成員和構造函數
const b: typeof Bar = Bar
// Java
console.log(b.lan)
複製代碼

private與單例模式

class Bar {
  private static instance: Bar
  private constructor () {}
  public static create() {
    if (!Bar.instance) {
      Bar.instance = new Bar()
    }
    return Bar.instance;
  }
}
let a = new Bar() // error,類的外部沒法訪問構造函數
let b = Bar.create() // ok
複製代碼

函數

函數類型

// 函數類型
const sum = (a: number, b: number): number => a + b
// 完整的函數類型,參數名能夠不一樣
const sum: (a: number, b: number) => number = (x: number, y: number) => x + y
複製代碼

推斷類型

// ts編譯器會自動推斷出x, y以及函數的返回值的類型
const sum: (a: number, b: number) => number = (x, y) => x + y
複製代碼

可選參數、默認參數、剩餘參數

// 可選參數,在參數旁使用`?`實現可選參數功能,可選參數必須在必須參數的最後
const sum = (a: number, b?: number): number => {
  if (b) {
    return a
  } else {
    return a + b
  }
}
// 參數的默認值,當參數的值是undefined時,會時候默認的參數
const sum = (a: number, b: number = 0): number => a + b
// 剩餘參數,使用省略號定義剩餘參數的數組
const sum = (...arguments: number[]) => {
  return arguments[0] + arguments[1]
}
複製代碼

this參數

this出如今函數的對象字面量中,默認爲any類型。能夠爲函數添加this參數,this參數是一個假參數,在參數列表中的第一位

interface Bar {
  name: string
}
function foo () {
  return {
    name: this.name // this爲any類型
  }
}
function bar (this: Bar) {
  return {
    name: this.name // this爲Bar類型
  }
}
複製代碼

重載

當咱們須要根據參數的不一樣類型,返回不一樣類型的結果時,可使用函數重載。爲同一個函數提供多個函數類型定義來進行函數重載。編譯器會根據這個列表去處理函數的調用。編譯器會依次查找重載列表,找到匹配的函數定義。

function foo(x: number): string
function foo(x: string): number
// function foo(x): any並非函數重載的一部分
function foo(x): any {
  if (typeof x === 'number') {
    return x + ''
  } else if (x === 'string') {
    return Number(x)
  }
}
複製代碼

泛型

使用泛型建立可重用的組件,使一個組件能夠支持多種數據類型

// echo函數就是泛型
function echo<T>(arg: T): T {
  return arg
}
// 明確傳入類型參數
echo<string>('Hello')
// 使用ts的類型推論,編譯器會根據參數自動確認T的類型
echo('Hello')
複製代碼

泛型變量

對於泛型參數,咱們必須把它看成任意或全部類型。

function echo<T>(arg: T): number {
  // error。布爾,數字類型是沒有length屬性的
  return arg.length
}
複製代碼

泛型接口

interface Foo {
  <T>(arg: T): T
}
const foo: Foo = <T>(arg: T): T => arg

// 將泛型參數看成接口的一個參數,明確泛型類型
interface Foo<T> {
  (arg: T): T
}
// arg將會被推導爲number類型
const foo: Foo<number> = (arg) => arg
複製代碼

泛型類

類分爲靜態部分和實例部分,泛型類指的是實例部分的類型,不能用於靜態部分

class Foo<T, U> {
  static name:T // error,靜態成員不能泛型類型
  constructor(public x: T, public y: U) {}
  getY(): U { return this.y }
  getX(): T { return this.x }
}
const foo = new Foo('CyberpuCk', 2077)
複製代碼

泛型約束

interface NameConstraint {
  name: string
}
// 約束了泛型參數T,必須包含name屬性,而不是任意類型
function witcher<T extends NameConstraint>(people: T): T {
  return people
}
// ok
witcher({ name: 'geralt' })
// error, 必須有name屬性
witcher('geralt')
複製代碼

在泛型中使用類類型

使用new (...any: []) => T,引用類類型

// c的實例必須是T類型,c必須是T類型自己
function create<T>(c: new(...any[]) => T): T {
  return new c();
}
複製代碼

合理的使用泛型(配合Axios使用的例子)

import axios from 'axios'

// 通用的返回結構
// 使用泛型,封裝通用的返回的數據接口
interface ResponseData<T = any> {
  code: number;
  result: T;
  message: string;
}

// 封裝的請求函數
// 請求用戶數據
function getUser<T>() {
  return axios.get<ResponseData<T>>('/user').then((res) => { return res.data }).catch(error => { }) } // 請求一組用戶數據 function getUsers<T>() { return axios.get<ResponseData<T>>('/users').then((res) => { return res.data }).catch(error => { }) } 複製代碼
// 用戶的接口
interface User {
  id: string,
  name: string
}
// 使用
async function test () {
  await getUser<User>()
  await getUsers<User[]>()
}
複製代碼

枚舉

數字枚舉

數字枚舉在不指定初始值的狀況下,枚舉值會從0開始遞增。若是一個成員初始化爲1,後面的成員會從1開始遞增。

字符串枚舉

枚舉中的每個成員,必須使用字符串進行初始化。

異構枚舉

枚舉的成員,數字成員和字符串成員混合。但最好不要這樣使用。

枚舉類型

當全部枚舉成員都具備字面量枚舉值時,枚舉具備了特殊的語義,枚舉能夠成爲一種類型。

enum Witcher {
  Ciri,
  Geralt
}
// ok
let witcher: Witcher = Witcher.Ciri
// error
let witcher: Witcher = 'V'
複製代碼

運行時枚舉

枚舉在運行時,是一個真實存在的對象,能夠被看成對象使用

enum Witcher {
  Ciri = 'Queen',
  Geralt = 'Geralt of Rivia'
}
function getGeraltMessage(arg: {[key: string]: string}): string {
  return arg.Geralt
}
getGeraltMessage(Witcher) // Geralt of Rivia
複製代碼

編譯時枚舉

雖然在運行時,枚舉是一個真實存在的對象。可是使用keyof時的行爲卻和普通對象不一致。必須使用keyof typeof才能夠獲取枚舉全部屬性名。

👻:這一部份內容在typescript中文網中是沒有的

enum Witcher {
  Ciri = 'Queen',
  Geralt = 'Geralt of Rivia'
}
type keys = keyof Witcher // toString, charAt………………
type keys = keyof typeof Witcher // Ciri, Geralt,全部的枚舉類型
複製代碼

const枚舉

const枚舉會在ts編譯期間被刪除,避免額外的性能開銷。

const enum Witcher {
  Ciri = 'Queen',
  Geralt = 'Geralt of Rivia'
}
const witchers: Witcher[] = [Witcher.Ciri, Witcher.Geralt]
// 編譯後
// const witchers = ['Queen', 'Geralt of Rivia']
複製代碼

枚舉的靜態方法和屬性

同名的命名空間和同名的枚舉類型,將會發生"聲明合併",命名空間export的函數和變量,將會成爲枚舉的靜態方法,靜態屬性。

enum Color { Blue, Yellow, Green }
namespace Color {
  export function print(color: Color): void {
    alert(Color[color])
  }
  export const name = 'colors'
}
Color.print(Color.Blue) // Blue
alert(Color.name) // colors
複製代碼

開放式枚舉

同名的枚舉將會被合併,可是你須要給同名的第二個枚舉,指定初始值。

enum Color { Red, Green, Blue }

enum Color { Yellow = 3, Black }
複製代碼

類型推論

// x被自動推斷爲number
let x = 2
// zoo被自動推斷爲(Rhino | Elephant | Snake)[]類型
// 若是Rhino,Elephant,Snake有同一種超類型Animal,zoo會被推斷爲Animal[]類型
let zoo = [new Rhino(), new Elephant(), new Snake()];
複製代碼

上下文類型

typescript會根據window.onmousedown的類型,推斷出右側的函數的類型。

👻:這一部份內容在typescript中文網中表現是不一致的。

// typescript能夠推斷出mouseEvent爲MouseEvent類型
window.onmousedown = function(mouseEvent) {
  // ok
  console.log(mouseEvent.button)
  // error
  console.log(mouseEvent.kangaroo)
}

window.onscroll = function(uiEvent) {
  // error,uiEvent會自動推斷出爲UIEvent類型,UIEvent類型不包含button屬性
  console.log(uiEvent.button)
}
複製代碼

noImplicitAny

開啓編譯選項noImplicitAny,當類型推斷只能推斷爲any類型,編譯器會發出警告

類型兼容性

typescript的類型兼容是基於結構的,不是基於名義的。下面的代碼在ts中是徹底能夠的,但在java等基於名義的語言則會拋錯。

interface Named { name: string }
class Person {
  name: string
}
let p: Named
// ok
p = new Person()

// 能夠直接使用const斷言(const斷言的介紹,在後面)
let c = 'python' as const
c = 'Swift' // error
複製代碼

協變(Covariant),逆變(Contravariant),雙向協變(Bivariant)

  • 協變(Covariant)父類 = 子類👌;子類 = 父類 🙅
  • 逆變(Contravariant)父類 = 子類 🙅;子類 = 父類 👌
  • 雙向協變(Bivariant)父類 = 子類 👌;子類 = 父類 👌

對象兼容性 協變(Covariant)

interface Foo {
  name: string;
}
interface Bar extends Foo {
  age: number;
}
let x!: Foo;
let y!: Bar;

x = y // ok
y = x // error
複製代碼

✨函數兼容性

函數參數類型兼容性 雙向協變(Bivariant)逆變(Contravariant)

函數參數的兼容性,具備雙向協變性

// 動物
interface Animal {
  name: string
}
// 貓
interface Cat extends Animal {
  color: string
}
// 美國短尾貓
interface USAShorthair extends Cat {
  age: number
}
type handle = (it: Cat) => void

// ok,是安全的
const handle1: handle = (it: Animal) => {}

// ok,不安全,可是被容許
// 由於type handle的參數是Cat類型,代表是能夠容許接受其餘,Cat的子類型的
// 若是it是USAShorthair類型,則會拒絕其餘Cat的子類,因此是不安全的
const handle2: handle = (it: USAShorthair) => {}
複製代碼
禁用函數參數雙向協變性 逆變(Contravariant)

開啓了strictFunctionTypes編譯選項,會禁用函數參數的雙向協變。參數類型是逆變的

type handle = (it: Cat) => void
// ok
const handle1: handle = (it: Animal) => {}
// error
const handle2: handle = (it: USAShorthair) => {}
複製代碼

函數返回值類型兼容性 協變(Covariant)

若是函數返回值是一個對象,兼容性同對象同樣

interface Foo {
  name: string;
}
interface Bar extends Foo {
  age: number;
}
// Bar是Foo的子類,返回值類型兼容性是協變的
// Foo(父類) = Bar(子類) ok
let x: () => Foo;
let y: () => Bar;
x = y; // ok
y = x; // error
複製代碼

函數參數的數量兼容性

函數能夠容許忽略一部分參數(具備逆變性?)

let x: (a: number) => void = (a) => {} // 參數數量少
let y: (a: number, b: string) => void = (a, b) => {} // 參數數量多
y = x; // ok
x = y; // error
複製代碼

枚舉兼容性

枚舉類型與數字類型兼容,不一樣枚舉類型之間不兼容

enum Bar { T, R }
enum Foo { T, R }
// ok
const a: number = Bar.T
// error
const b: Foo = Bar.T
複製代碼

類兼容性

類類型的兼容性和對象字面量相似。但類類型只會比較類的實例部分,靜態成員和構造函數不在兼容性的比較範圍內。

class Bar {
  constructor(public name: string) {}
}
class Foo {
  constructor(public name: string, public age: number) {}
}
class Faz {
  constructor(public age: number) {}
}
class Boo {
  static version: number
  constructor(public name: string) {}
}
let bar!:Bar
let foo!:Foo
let faz!:Faz
let boo!:Boo
foo = bar // error,缺乏age實例屬性,兼容性的規則和對象相似
bar = foo // ok
bar = faz // error,name和age不兼容
boo = bar // ok,靜態成員不會比較兼容性
複製代碼

泛型兼容性

泛型進行兼容性比較時,須要指定泛型參數後比較。當沒有指定泛型參數時,泛型參數默認爲any類型。

type Bar<T> = {
  name: T
}
type Foo<U> = {
  name: U
}
type Far<R> = {
  name: R
}
let a!:Bar<string>
let b!:Foo<string>
let c!:Far<number>
a = b // ok
b = c // error
複製代碼

兼容性總結

  • 對象兼容性 協變(Covariant)
  • 函數兼容性
    • 函數參數兼容性 雙向協變(Bivariant)-更安全-> 逆變(Contravariant)
    • 函數返回值兼容 協變(Covariant)
    • 函數參數個數 逆變(Contravariant)
  • 枚舉兼容性(數字 <= 枚舉 ok,枚舉a <= 枚舉b error)
  • 類兼容性(和對象兼容性類型相似)
  • 泛型兼容性(須要指定泛型參數後比較)

名義化類型

ts的兼容性是基於結構的。但有時,咱們確實想要區分,結構相同的兩種類型。

使用字面量類型

使用字面量類型區分結構相同的類型

interface People<T extends string> {
  name: string,
  age: number,
  color: T
}
let blackPeople!: People<'black'>
let whitePeople!: People<'white'>
// 結構相同,可是類型並不兼容
// 例子沒有涉及任何種族主義思想
blackPeople = whitePeople // error
whitePeople = blackPeople // error
複製代碼

使用枚舉

interface Foo { name: string }
enum FooEnum {}
interface Boo { name: string }
enum BooEnum {}
type FooTitular = Foo & FooEnum
type BooTitular = Boo & BooEnum
let a:FooTitular = { name: '123' } as FooTitular
let b:BooTitular = { name: '456' } as BooTitular
// 結構相同可是不兼容
a = b // error
b = a // error
複製代碼

使用接口

爲類型添加一個無關的屬性,打破結構兼容性,這個無關的屬性的命名習慣,一般是以_開頭,以Brand結尾

interface A extends String {
  _aBrand: number;
}
interface B extends String {
  _bBrand: number;
}
let a: A = 'a' as any;
let b: B = 'b' as any;
// 結構相同,可是類型不兼容
a = b; // error
b = a; // error
複製代碼

✨高級類型

交叉類型

將多種類型疊加成爲一種類型

function assign<T extends object, U extends object>(x: T, y: U): T & U {
  return { ...x, ...y }
}
class Foo {
  constructor(public foo:string) {}
}
class Bar {
  constructor(public bar: string) {}
}
const boo = assign(new Foo('foo'), new Bar('bar')) // boo是Bar & Foo的交叉類型
boo.bar // ok
boo.foo // ok
複製代碼

聯合類型

聯合類型表示一個值能夠是幾種類型之一。當一個值是聯合類型時,咱們能肯定它包含多種類型的共有成員。

let a!:Boo | Foo | Baz
a.sex // error,當a是Boo,Baz類型時,是沒有sex屬性
(a as Foo).sex // ok
複製代碼

類型保護

自定義類型保護

定義一個特殊的函數,返回值爲arg is type類型謂詞。arg是自定義類型保護函數中的一個參數。

interface Boo { name: string }
interface Foo { sex: boolean }
interface Baz { age: number }
type BooFooBaz = Boo | Foo | Baz
let a!: Boo | Foo | Baz

function isBoo(arg: BooFooBaz): arg is Boo {
  return (arg as Boo).name !== 'undefined'
}

alert(a.name) // error
if (isBoo(a)) {
  alert(a.name) // ok
}
複製代碼

typeof類型保護

針對number、string、boolean、symbol提供類型保護。咱們能夠沒必要將typeof抽象成返回類型謂詞的特殊函數。

let a!:number | string
if (typeof a === 'string') {
  alert(a.length) // ok
}
if (typeof a === 'number') {
  alert(a - 1) // ok
}
複製代碼

instanceof類型保護

針對類類型提供類型保護

class Foo {
  getFoo(): void {}
}
class Bar {
  getBar(): void {}
}
let a!: Bar | Foo

if (a instanceof Foo) {
  a.getFoo() // ok
  a.getBar() // error
}
if (a instanceof Bar) {
  a.getBar() // ok
  a.getFoo() // error
}
複製代碼

in類型保護

interface A { x: number }
interface B { y: string }
function doStuff(q: A | B) {
  // 判斷是否有x屬性
  if ('x' in q) {
    alert(q.x)
  } else {
    alert(q.y)
  }
}
複製代碼

字面量類型保護

interface Foo {
  name: string
  type: 'foo'
}
interface Bar {
  name: string
  type: 'bar'
}
function temp (arg: Foo | Bar) {
  if (arg.type === 'foo') {
    // Foo類型
  } else {
    // Bar類型
  }
}
複製代碼

null類型

null, undefined能夠賦值給任何類型。在tsconfig.js中添加strictNullChecks的配置,能夠阻止這種行爲。開啓strictNullChecks後,一個變量默認不在自動包含nullundefined類型。

// 開啓strictNullChecks前
let a: string = '123'
a = null // ok

// 開啓strictNullChecks後
let b: string = '123'
b = null // error
複製代碼

斷言排除undefined,null類型

在變量名後添加!,能夠斷言排除undefined和null類型

let a: string | null | undefined
a.length // error
a!.length // ok
複製代碼

✨類型別名

type Foo = string
type Bar = {
  name: string,
  age: number
}
// 類型別名也可用來聲明函數
type Func = {
  (a: number): number;
  (a: string): string;
}
// 類型別名也能夠是泛型
type Boo<T> = {
  key: T
}
// 類型別名中能夠引用自身
type LinkListNode<T> = {
  value: T,
  next: LinkListNode<T> | null,
  prev: LinkListNode<T> | null
}
複製代碼

類型別名和接口的區別

  1. 類型別名能夠爲任何類型引入名稱。例如基本類型,聯合類型等
  2. 類型別名不支持繼承
  3. 類型別名不會建立一個真正的名字
  4. 類型別名沒法被實現,而接口能夠被派生類實現
  5. 類型別名重名時編譯器會拋出錯誤,接口重名時會產生合併

字符串字面量類型

let a!: 'js'
a = 'js' // ok
a = 'java' // error

let b!: 'c#' | 'c++'
b = 'c#' // ok
b = '.net' // error
複製代碼

數字字面量類型

let x!: 1
x = 2 // error

let y!: 1 | 2
y = 2 // ok
y = 3 // error
複製代碼

索引類型

索引類型查詢操做符

keyof T的結果爲T上公共屬性名的聯合

type Keys<T> = keyof T
interface Witcher {
  name: string,
  age: number
}
// Witcher公共屬性名的聯合
let keys: Keys<Witcher>; // name | age
複製代碼
type Keys<T> = keyof T
type Witchers = [string, string]
let keys: Keys<Witchers>; // '0' | '1' | length | toString……

type Keys<T> = keyof T
type Witchers = Array<string>
let keys: Keys<Witchers>; // number | length | toString……
複製代碼
function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
  return names.map(n => o[n])
}
const witcher = {
  name: '傑洛特',
  age: 88
}
// (string | number)[]
// ['傑洛特', '88']
const values = pluck(witcher, ['name', 'age'])
複製代碼

索引訪問操做符

T[K],索引訪問操做符。可是須要確保K extends keyof T

function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}
複製代碼

映射類型

從舊類型中建立出一種新的類型,映射類型

// 只讀映射類型
type CustomReadonly<T> = {
  readonly [K in keyof T]: T[K]
}
// 可選映射類型
type CustomPartial<T> = {
  [K in keyof T]?: T[K]
}
// 可爲空可爲undefined映射類型
type NullUndefinedable<T> = {
  [K in keyof T]: T[K] | null | undefined
}
複製代碼
type CustomPick<T, K extends keyof T> = {
  [P in K]: T[P];
}
interface Bar {
  name: string,
  age: number,
  sex: boolean
}
// 提取出指定的key的類型
// Foo = { name: string, age: number }
type Foo = CustomPick<Bar, 'name' | 'age'>
複製代碼
type CustomRecord<K extends string, T> = {
  [P in K]: T;
}
// { name: string, title: string, message: string }
type Bar = CustomRecord<'name' | 'title' | 'message', string>
複製代碼

內置映射類型

Exclude

Exclude<T, U>,從T出提取出,不能賦值給U的類型

// string | boolean
// string | boolean 不能賦值給number類型
type Foo = Exclude<string | number | boolean, number>
複製代碼

Extract

Extract<T, U>,從T出提取出,能賦值給U的類型

// number
// 只有number能給賦值給number類型
type Foo = Extract<string | number | boolean, number>;
複製代碼

NonNullable

NonNullable<T>,從T中剔除null和undefined類型

type Bar = string | null | undefined
// Foo
type Foo = NonNullable<Bar>;
let a!: Foo;
// error
a = null
複製代碼

基於NonNullable<T>封裝,剔除屬性中null和undefined類型

type NonNullUndefinedable<T> = {
  [K in keyof T]: NonNullable<T[K]>
}
interface Bar {
  name: string | null | undefined
  age: number | null | undefined
  sex: boolean | null | undefined
}
// Foo { name: string, age: number, sex: boolean }
type Foo = NonNullUndefinedable<Bar>
複製代碼

ReturnType

ReturnType<T>,獲取T的返回值類型

type BarCall = () => number
// number
type BarReturn = ReturnType<BarCall>
複製代碼

InstanceType

InstanceType<T>,獲取T的實例類型

class Bar {}
// Bar, Bar的構造函數的實例類型,就是Bar類型
type Foo = InstanceType<typeof Bar>
複製代碼

Omit

Omit在ts3.5版本已經成爲內置類型(見下文)

// Exclude<keyof T, K>從key of T中出剔除全部K的類型
// Pick<T, U>映射出只有U屬性的新類型
// 模擬Omit映射類型
type CustomOmit<T, K> = Pick<T, Exclude<keyof T, K>>
複製代碼

lib.d.ts

在安裝typescript時,會安裝一系列聲明文件lib.dom.d.tslib.es2015.core.d.ts等。它們包含了javaScript運行時以及dom中存在各類常見的環境聲明

// lib.dom.d.ts內容節選

/** A window containing a DOM document; the document property points to the DOM document loaded in that window. */
interface Window extends EventTarget, WindowTimers, WindowSessionStorage, WindowLocalStorage, WindowConsole, GlobalEventHandlers, IDBEnvironment, WindowBase64, AnimationFrameProvider, WindowOrWorkerGlobalScope, WindowEventHandlers {
  readonly applicationCache: ApplicationCache;
  readonly caches: CacheStorage;
  readonly clientInformation: Navigator;
  readonly closed: boolean;
  readonly crypto: Crypto;
  customElements: CustomElementRegistry;
  defaultStatus: string;
  readonly devicePixelRatio: number;
  readonly doNotTrack: string;
  readonly document: Document;
  // ...more
}
複製代碼

修改原始類型

建立globals.d.ts全局的聲明文件,在全局的聲明文件中修改原始的類型

// globals.d.ts

// 修改Window接口,添加helloworld方法
interface Window {
  helloworld(): 'helloworld'
}
// 修改Date的靜態成員,添加today方法
interface DateConstructor {
  today(): Date
}

// 使用
window.helloworld()
Date.today()
複製代碼

流動的類型

複製類型

import配合namespace或者module能夠實現類型的複製。

class Foo {}
const Bar = Foo // Bar只是Foo的引用,不是類型
let bar: Bar // error,沒有Bar類型
複製代碼
namespace classs {
  export class Foo {}
}
import Bar = classs.Foo // 能夠實現複製類型
let bar: Bar // ok
複製代碼

獲取變量的類型

使用lettypeof能夠獲取變量自身的類型(若是是使用const, 使用typeof獲取的是字面量類型)

let foo = 'foo'
let bar!: typeof foo
bar = 'bar' // ok, bar爲string類型
bar = 123 // error


let foo = [1, 2, 3]
let bar!: typeof foo
bar = [4, 5, 6] // ok
bar = ['4', '5', '6'] // error, bar爲number[]類型
複製代碼

獲取類(實例)成員的類型

class Foo {
  constructor (public name: string) {}
}
declare let _foo:Foo
// bar爲string類型,_foo是Foo的實例
let bar: typeof _foo.name
複製代碼

獲取字面量類型

使用const關鍵字與typeof,能夠獲取字面量類型

const foo = 'foo'
let bar!: typeof foo
bar = 'bar' // error
bar = 'foo' // ok, foo類型爲foo
複製代碼

命名空間

使用命名空間組織代碼,避免命名衝突。若是外部想訪問命名空間內部的內容,命名空間中的內容,須要使用export導出

namespace Lib {
  export namespace Math {
    export function sum(a: number, b: number): number {
      return a + b
    }
  } 
}
Lib.Math.sum(1, 2)
複製代碼

命名空間別名

import alias = namespace.namespace,能夠爲命名空間建立別名

namespace Lib {
  export namespace Math {
    export namespace Geometry {
      export function pythagoreanTheorem(x: number, y: number): number {
        return x * x + y * y
      }
    }
  } 
}
// 使用時,會有多個層級的關係
Lib.Math.Geometry.pythagoreanTheorem(1, 2)
// 使用命名空間別名
import Geometry = Lib.Math.Geometry
Geometry.pythagoreanTheorem(1, 2)
複製代碼

使用其餘javascript庫

ts在使用,使用js編寫的類庫時,須要類型聲明文件xx.d.ts,大部分類庫一般會暴露一個頂級的對象,可使用命名空間表示它們。舉一個例子。

// node_modules/custom-lib/index.d.ts
export declare namespace Lib {
  export namespace Geometry {
    export function pythagoreanTheorem(x: number, y: number): number {
      return x * x + y * y
    }
  }
  } 
}
export declare const gravitationalConstant: number
複製代碼
// src/main.ts
import { Lib, gravitationalConstant } from 'custom-lib'

Lib.Geometry.pythagoreanTheorem(gravitationalConstant, 2)
複製代碼

✨一些手冊指南中沒有說起的內容

只讀數組的新語法

// 原來的語法
const bar: ReadonlyArray<string> = ['Hello', 'World']
// 新增的語法
const boo: readonly string[] = ['Hello', 'World']
複製代碼

const類型斷言

使用const斷言,typescript會爲變量添加一個自身的字面量類型。舉一個例子

// ts自動推導爲x添加`string`類型
// let x: string = '123'
let x = '123'
// x能夠被再次賦值
x = '456'

// 使用const斷言,會爲x添加自身的字面量類型
// let x: '123' = '123'
let x = '123' as const
// error,x不能被從新賦值
x = '456'
複製代碼

使用const斷言時:

  1. 對象字面量的屬性,得到readonly的屬性,成爲只讀屬性
  2. 數組字面量成爲readonly tuple只讀元組
  3. 字面量類型不能被擴展(好比從hello類型到string類型)
// type '"hello"'
let x = "hello" as const
// type 'readonly [10, 20]'
let y = [10, 20] as const
// type '{ readonly text: "hello" }'
let z = { text: "hello" } as const
複製代碼

const斷言支持兩種語法,as語法,在非tsx文件中可使用<const>語法

// type '"hello"'
let x = <const>"hello"
// type 'readonly [10, 20]'
let y = <const>[10, 20]
// type '{ readonly text: "hello" }'
let z = <const>{ text: "hello" }
複製代碼

unknown類型

unknown類型和any類型相似。與any類型不一樣的是。unknown類型能夠接受任意類型賦值,可是unknown類型賦值給其餘類型前,必須被斷言。

unknown對那種想表示是任何值,可是使用前必須執行類型檢查的api頗有用。

let x:any = 'Hello'
// ok
let y:number = x

let x: unknown = 'Hello'
// error
let y: number = x
// ok,必須通過斷言處理才能賦值給確認的類型
let z: number = x as number
複製代碼

✨✨✨infer

一開始,我很難理解infer究竟是作什麼的,咱們從兩個簡單的示例開始

type Pro<T> = T extends Promise<infer R> ? Promise<R> : T

type Bar = Pro<Promise<string[]>> // Bar = Promise<string[]>,Promise<string[]>知足Promise<R>的約束
type Boo = Pro<number> // Boo = number
複製代碼

type Pro,會檢查泛型參數T,若是泛型參數T知足Promise的約束條件,返回Promise<R>的類型,不然返回T類型

type Param<T> = T extends (param: infer P) => any ? P : T

type getUser = (id: number) => object

type Foo = Param<getUser> // Foo等於getUser的參數類型,number類型
type Bar = Param<boolean> // Bar等於boolean類型
複製代碼

type Param,會檢查泛型參數T,若是泛型參數T知足(param: infer P) => any的約束,會返回參數的類型,不然返回T類型

infer映射類型

typescript中內置的infer映射類型

type ReturnType

提取獲取函數的返回值的類型

type Foo = ReturnType<() => string[]> // Foo = string[]
複製代碼

type ReturnType內部的具體實現

// 自定義ReturnType映射類型
type CustomReturnType<T> = T extends (...arg: any[]) => infer P ? P : T 
type Foo = CustomReturnType<() => string[]> // Foo = string[]
複製代碼
type ConstructorParameters

提取構造函數的參數類型

class Foo {
  constructor (public name: string, public age: number) {}
}
type Params = ConstructorParameters<typeof Foo> // [string, number]
複製代碼

type ConstructorParameters內部具體實現

// ConstructorParameters映射類型
type CustomConstructorParameters<T extends new (...args: any[]) => any> = T extends new (...args: infer P) => any ? P : never
type Params = CustomConstructorParameters<typeof Foo> // [string, number]
複製代碼

元組類型 -> 聯合類型

如何將 [string, number] 轉變成 string | number?

type CustomTuple = [string, number, boolean]

type TupleToUnion<T> = T extends Array<infer P> ? P : never

type CustomUnion = TupleToUnion<CustomTuple> // string | number | boolean
複製代碼

聯合類型 -> 交叉類型

《TypeScript深刻淺出》中的例子,在目前的版本(3.6.4)中存在一些問題

type Omit

Omit是3.5版本中新增的映射類型, 從T中刪除全部的K類型。

interface Foo {
  name: string
  age: number
}

// Bar = { name: string }
type Bar = Omit<Foo, 'age'>
複製代碼

在以前的版本中可使用type Omit<T, K> = Pick<T, Exclude<keyof T, K>>實現

參考

感謝大佬們的付出❤️

相關文章
相關標籤/搜索