爲了趕在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,表示一個已知元素數量和類型的數組。訪問跨界元素時,ts會拋出錯誤。前端
👻:typescript中文網中的內容是,訪問跨界元素,使用聯合類型。是過期的內容。vue
const tuple: [number, string] = [2077, 'Cyberpunk']
複製代碼
能夠爲一組數據提供更語義化的名稱java
enum language {CPP = 1, JS = 2, JAVA = 3}
const lan: language = language.CPP // 1
// Enum類型,也能夠經過枚舉的值,獲取枚舉的名稱
const lanName: string = language[2] // 2
複製代碼
Any爲不清楚類型的變量指定類型,變量將不會進行類型檢查。node
Void表示沒有任何類型。若是一個變量爲Void類型,只能賦予undefined或者null。python
null, undefined是全部類型的子類型。null和undefined能夠賦值給number或者string類型。ios
若是tsconfig.json的配置項strictNullChecks
設置爲true,那麼null, undefined只能賦予它們各自的類型以及Void類型。c++
Never表示永遠不存在的類型。好比一個函數老是拋出錯誤,而沒有返回值。或者一個函數內部有死循環,永遠不會有返回值。函數的返回值就是Never類型。git
object表示非原始類型,也就是除number,string,boolean,symbol,null或undefined以外的類型。好比一個函數接受一個對象做爲參數,可是參數上的key和value是不肯定的,此時參數可使用object類型。github
類型斷言告訴編譯器,咱們本身確切的知道變量的類型,而不須要進行類型檢查。
// as語法或是尖括號語法
alert((temp as string).length)
alert((<string>temp).length)
複製代碼
S as T
,只有當S
是T
的子集或者T
是S
的子集時,斷言才能成功
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: '拜年劍法'
}
複製代碼
接口能夠繼承一個類,可是接口只會繼承類的定義,但不包括類的具體實現。接口會繼承類的private
和protected
成員的修飾符。 當一個接口,繼承了包含了private
和protected
成員的類時,該接口只能被該類和該類的子類所實現。
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 }
}
複製代碼
readonly
能夠將實例的屬性,設置爲只讀在構造函數的參數中使用private
、protected
、public
或者readonly
,能夠將聲明和賦值合併至一處
// Boo,Far聲明name屬性的方式是一致的
class Boo {
public name: string
constructor (theName: string) {
this.name = theName
}
}
class Far {
constructor (public name: string) {
}
}
複製代碼
可使用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
}
}
複製代碼
在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)
複製代碼
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出如今函數的對象字面量中,默認爲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();
}
複製代碼
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枚舉會在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
,當類型推斷只能推斷爲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
複製代碼
interface Foo {
name: string;
}
interface Bar extends Foo {
age: number;
}
let x!: Foo;
let y!: Bar;
x = y // ok
y = x // error
複製代碼
函數參數的兼容性,具備雙向協變性
// 動物
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) => {}
複製代碼
開啓了strictFunctionTypes
編譯選項,會禁用函數參數的雙向協變。參數類型是逆變的
type handle = (it: Cat) => void
// ok
const handle1: handle = (it: Animal) => {}
// error
const handle2: handle = (it: USAShorthair) => {}
複製代碼
若是函數返回值是一個對象,兼容性同對象同樣
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
複製代碼
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
}
複製代碼
針對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
}
複製代碼
針對類類型提供類型保護
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
}
複製代碼
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, undefined能夠賦值給任何類型。在tsconfig.js中添加strictNullChecks
的配置,能夠阻止這種行爲。開啓strictNullChecks
後,一個變量默認不在自動包含null
和undefined
類型。
// 開啓strictNullChecks前
let a: string = '123'
a = null // ok
// 開啓strictNullChecks後
let b: string = '123'
b = null // error
複製代碼
在變量名後添加!
,能夠斷言排除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
}
複製代碼
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<T, U>
,從T
出提取出,不能賦值給U
的類型
// string | boolean
// string | boolean 不能賦值給number類型
type Foo = Exclude<string | number | boolean, number>
複製代碼
Extract<T, U>
,從T
出提取出,能賦值給U
的類型
// number
// 只有number能給賦值給number類型
type Foo = Extract<string | number | boolean, number>;
複製代碼
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<T>
,獲取T
的返回值類型
type BarCall = () => number
// number
type BarReturn = ReturnType<BarCall>
複製代碼
InstanceType<T>
,獲取T
的實例類型
class Bar {}
// Bar, Bar的構造函數的實例類型,就是Bar類型
type Foo = InstanceType<typeof Bar>
複製代碼
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>>
複製代碼
在安裝typescript
時,會安裝一系列聲明文件lib.dom.d.ts
,lib.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
複製代碼
使用let
和typeof
能夠獲取變量自身的類型(若是是使用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)
複製代碼
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斷言,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斷言時:
readonly
的屬性,成爲只讀屬性readonly tuple
只讀元組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
類型和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究竟是作什麼的,咱們從兩個簡單的示例開始
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
類型
typescript中內置的infer映射類型
提取獲取函數的返回值的類型
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[]
複製代碼
提取構造函數的參數類型
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)中存在一些問題。
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>>
實現
感謝大佬們的付出❤️