常用类型
string
-
字符串
-
let str: string = 'hello ts'
number
-
数字
-
let num: number = 100
boolean
-
布尔
-
let bool: boolean = true
数组
-
type[]
-
Array 泛型写法
-
let arr: number[] = [1, 2, 3] let arr2: Array<number> = [1, 2, 3]
any
- 不希望某个特定值导致类型检查错误
- 设置为any之后不会进行类型判断,不推荐
变量上的类型注释
基元类型
-
let myName: string = 'zzy' // :string 冒号+类型
函数
-
function greet(name: string): void { console.log('hello,' + name) } // : string 参数类型注释 // : void 返回值类型注释
对象类型
-
function printCoord(pt: { x: number; y: number; z ?: number }) { console.log('x坐标为:' + pt.x + ' y坐标为:' + pt.y + ' z坐标为:' + pt.z) } printCoord({ x: 2, y: 3 }) // : { x: number; y: number; z?: number } 参数的类型注释是对象类型 用分号或逗号分割 ?:代表可选参数
联合类型
-
let id: number | string function printId(id: string | number): void {} printId(1) printId('a') // 两个或多个其他类型组成的类型
类型别名
-
`type 类型名称` 可以多次复用类型 type定义的是变量的类型,而不是变量 // 对象类型 type Point = { x: number y: number } function printCoord2(pt: Point) {} printCoord2({ x: 0, y: 0 }) // 联合类型 type ID = number | string function printId2(id: ID) {} printId2(100) printId2('hello') // 基元类型 type USerName = string function userInfo(str: string): USerName { return str.slice(0, 2) } let user = userInfo('hello') user = 'newHello'
接口
-
interface Locate { x: number y: number } function getLocation(pt: Locate) {} getLocation({ x: 100, y: 100 })
接口与类型别名的异同
-
// 1.通过接口定义类型 interface Animal { name: string } interface Bear extends Animal { honey: boolean } const bear: Bear = { name: 'winie', honey: true } console.log(bear.name) console.log(bear.honey) // 2.通过类型别名定义类型 type Animal2 = { name: string } type Bear2 = Animal2 & { honey: boolean } const bear2: Bear2 = { name: 'winie2', honey: false } console.log(bear2.name) console.log(bear2.honey) // 3.通过接口向现有类型添加新字段 interface MyWindow { count: number; } interface MyWindow { title: string; } const w: MyWindow = { title: 'hello ts', count: 100 } // 4.通过type不可以向现有类型添加新字段 // 类型创建后不可以更改 // type MyWindow2 = { // count1: number // } // type MyWindow2 = { // count: number // }
类型断言
-
//与类型注释一样,类型断言由编译器删除,不会影响代码运行时的行为 // as 具体类型 放在需要断言语句的后面 const myCanvas = document.getElementById('main_canvas') as HTMLCanvasElement // <具体类型> 放在需要断言语句的前面 const myCanvas = <HTMLCanvasElement>document.getElementById('main_canvas')
文字类型
-
let tsetStr = 'a' tsetStr = 'newA' // const tsetStr2 = 'b' // tsetStr2 = 'newB'' let test: 'hello' = 'hello' // test = 'world' // 规定了入参的字符串 function printText(alignment: 'left' | 'right' | 'center') {} printText('left') // 通过接口实现文字类型组合 interface Options { width: number } function configure(x: Options | 'auto') {} configure({ width: 100 }) configure('auto') // 布尔文字类型 let b1: true = true let b2: false = false // 文字推理 function handleRequest( url: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE' ) {} // 因为ts默认会将对象类型隐式断言为字符串 // 方法1:通过给函数入参时将req.method断言为'GET’ const req = { url: 'http://example.com', method: 'GET' } handleRequest(req.url, req.method as 'GET') // 方法2:或在定义req.method时将req.method类型断言为'GET'类型 const req2 = { url: 'http://example.com', method: 'GET' as 'GET' } handleRequest(req2.url, req2.method) // 方法3:在定义req时将req断言为const const req3 = { url: 'http://example.com', method: 'GET' } as const handleRequest(req3.url, req3.method)
null和undefined
-
// null表示不存在 // undefined表示未初始化的值 let un: undefined = undefined let nu: null = null // let st: string = undefined; function live(x?: number | null) { // !.表示绝对不可能是undefined或者null console.log(x!.toFixed()); // ?.表示允许为undefined或者null,即可以不传 console.log(x?.toFixed()); }
枚举
-
enum Direction { Up = 1, Down = 2, Left, Right } console.log(Direction.Up) -
编译后的js
-
"use strict"; var Direction; (function (Direction) { Direction[Direction["Up"] = 1] = "Up"; Direction[Direction["Down"] = 2] = "Down"; Direction[Direction["Left"] = 3] = "Left"; Direction[Direction["Right"] = 4] = "Right"; })(Direction || (Direction = {})); console.log(Direction.Up);
bigint和symbol
-
// bigint:非常大的整数 // symbol:全局唯一引用 const bi: bigint = BigInt(100) const bi2: bigint = 100n const sy = Symbol('sym') const sy2 = Symbol('sym2') // console.log(sy === sy2) // 报错
类型缩小
-
宽类型转化为窄类型的过程,常用于处理联合类型
-
function padLeft(padding: number | string,input:string): string { // 将特殊的检查称之为类型防护 // 将类型细化为比声明更具体的类型的过程称之为类型缩小 if(typeof padding === 'number') { return new Array(padding + 1).join(" ") + input; } return padding + input }
typeof 类型守卫
-
function printAll(strs: string | string[] | null) { if (typeof strs === 'object' && strs !== null) { for (const s of strs) { console.log(s) } } else if (typeof strs === 'string') { console.log(strs) } else { console.log(strs) } } printAll(['a', 'b']) printAll('aa') printAll(null)
真值缩小
-
条件、 && 、|| 、f语句 、布尔否定(!)// 0 NaN ""(空字符串) 0n(bigint零) null undefined 会被强制转换为false // 这两个结果也返回true Boolean('hello') // type: boolean, value: true !!'world' // type: boolean, value: true function getUsersOnline(count: number) { if (count) { return `现在共有${count}人在线!` } return '现在0人在线. :("' } function printAll2(strs: string | string[] | null) { if (strs && typeof strs === 'object') { for (const s of strs) { console.log(s) } } else if (typeof strs === 'string') { console.log(strs) } else { console.log(strs) } } printAll2(['a', 'b']) printAll2('aa') printAll2(null)
等值缩小
-
===、!==、==、!=function example(x: string | number, y: string | boolean) { if (x === y) { x.toUpperCase() y.toLowerCase() } else { console.log(x, y); } }
in操作符缩小
-
"value" in x // x具有可选或必须属性的类型的值时才为truetype Fish = { swim: () => void } type Bird = { fly: () => void } type Human = { swim?: () => void fly?: () => void } function move(animal: Fish | Bird | Human) { if ('swim' in animal) { return (animal as Fish).swim() } return (animal as Bird).fly() }
instanceof操作符缩小
-
x instanceof Foofunction logValue(x: Date | string) { if (x instanceof Date) { console.log(x.toUTCString()) } else { console.log(x.toUpperCase()) } } logValue(new Date()) logValue('hello ts')
分配缩小
-
let x = Math.random() < 0.5 ? 10 : 'hello ts' // 将x的类型设置为 string | number x = 1 x = 'world'
控制流分析
-
function example() { let x: string | number | boolean x = Math.random() < 0.5 // x为boolean类型 if (Math.random() < 0.5) { x = 'hello' // x为string类型 } else { x = 100 // x为number类型 } return x // x为string | number }
使用类型谓词
-
type Fish1 = { name: string swim: () => void } type Bird1 = { name: string fly: () => void } function isFish(pet: Fish1 | Bird1): pet is Fish1 { // 返回为true则代表返回类型为Fish1 // 判断pet中是否含有swim属性 return (pet as Fish1).swim !== undefined } function getSmallPet(): Fish1 | Bird1 { { let fish: Fish1 = { name: 'sharkey', swim: () => {} } let bird: Bird1 = { name: 'sparrow', fly: () => {} } return true ? bird : fish } } let pet = getSmallPet() if (isFish(pet)) { pet.swim() } else { pet.fly() } const zoo: (Fish1 | Bird1)[] = [getSmallPet(), getSmallPet(), getSmallPet()] const underWater: Fish1[] = zoo.filter(isFish) const underWater2: Fish1[] = zoo.filter(isFish) as Fish1[] const underWater3: Fish1[] = zoo.filter((pet): pet is Fish1 => { if (pet.name === 'frog') { return false } return isFish(pet) })
受歧视的unions
-
// 求面积 interface Circle { kind: 'circle' radius: number } interface Square { kind: 'square' sideLength: number } type Shape = Circle | Square function getArea(shape: Shape): number { switch (shape.kind) { case 'circle': return Math.PI * shape.radius ** 2 case 'square': return shape.sideLength ** 2 } }
never与穷尽性检查
-
never => 不应该存在的状态interface Circle { kind: 'circle' radius: number } interface Square { kind: 'square' sideLength: number } type Shape1 = Circle | Square function getArea(shape: Shape1): number { switch (shape.kind) { case 'circle': return Math.PI * shape.radius ** 2 case 'square': return shape.sideLength ** 2 default: // never可以进行穷尽性检查 const _exhaustiveCheck: never = shape return _exhaustiveCheck } }
函数
函数类型表达式
-
fn: (a: string) => voidtype GreetFn = (a: string) => void function greet(fn: GreetFn): void { fn('hello world') } function printTo(s: string) { console.log(s) } greet(printTo)
调用签名
-
type DescribableFn = { description: string (someArg: number): boolean } function doSomething(fn: DescribableFn) { console.log(fn.description + ' returned:' + fn(6)) } function fn1(n: number) { console.log(n) return true } fn1.description = 'hello' doSomething(fn1)
构造签名
-
class Ctor { s: string constructor(s: string) { this.s = s } } type someConstructor = { new (s: string): Ctor } function fn(ctor: someConstructor) { return new ctor('hello') } const f = fn(Ctor) console.log(f.s) interface CallOrCtor { new (s: string): Date (n?: number): number } function fn1(date: CallOrCtor) { let d = new date('2021-12-21') let n = date(100) }
泛型函数
-
输入的类型与输出的类型有关,或者两个输入的类型以某种方式相关联
类型推断
-
function firstElement(arr: any[]) { return arr[0] } firstElement(['a', 'b', 'c']) // 泛型的作用是确保输入的元素类型和输出的元素类型一致 function firstElement1<T>(arr: T[]): T | undefined { return arr[0] } // 可以不用加<string>,ts会自动推断类型,加上反而会限制参数类型 firstElement1<string>(['a', 'b', 'c']) firstElement1(['a', 'b', 'c']) firstElement1<number>([1, 2, 3]) firstElement1<undefined>([]) function map<I, O>(arr: I[], func: (arg: I) => O): O[] { return arr.map(func) } // 根据类型推断I类型为string,O类型为number const parsed = map(['1', '2', '3'], (n) => parseInt(n))
限制条件
-
// 限制条件并非扩展条件 要求传入的a和b必须包含length属性 function longest<T extends { length: number }>(a: T, b: T) { if (a.length >= b.length) { return a } else { return b } } const longerArr = longest([1, 2], [2, 3, 4]) const longerStr = longest('a', 'b')
使用受限值
-
function minimum<T extends { length: number }>(obj: T, minimum: number): T { if (obj.length >= minimum) { return obj } else { // 不能返回确定的对象,需要根据入参来确定返回的类型1,应该返回的是一个泛型 return { length: minimum } // 报错 } }
指定类型参数
-
function combine<T>(arr1: Array<T>, arr2: T[]): T[] { return arr1.concat(arr2) } // 指定参数类型(不推荐,一般<string | number>用是用来指定入参类型) const arr = combine<string | number>([1, 2, 3], ['a', 'b', 'c']) console.log(arr)
编写优秀通用函数准则
-
可能的情况下,使用类型参数本身,而不是对其进行约束
-
总是尽可能少地使用类型参数
-
如果一个类型的参数只出现在一个地方,请考虑是否真的需要
-
// 1.尽可能使用类型参数本身T,而不是使用extends对其进行约束(推荐) ts推断返回的类型是T function firstEl<T>(arr: T[]) { return arr[0] } // 不推荐 ts推断返回的类型是any function firstEl2<T extends any[]>(arr: T) { return arr[0] } const a = firstEl([1, 2, 3]) const b = firstEl2([1, 2, 3]) // 2.使用更少的类型参数,不要单独定义一个类型参数 (推荐) function filter<T>(arr: T[], func: (arg: T) => boolean) { return arr.filter(func) } // 不推荐 function filter2<T, F extends (arg: T) => boolean>(arr: T[], func: F) { return arr.filter(func) } // 3.如果一个类型的参数只出现在一个地方,没有必要再次定义 (推荐) function greet(s: string) { console.log('hello', +s) } // 不推荐 function greet2<Str extends string>(s: Str) { console.log('hello', +s) }
可选参数
-
// ? 表示可以选择是否传入参数 function f(x?: number) { // ... } // 也可以设置默认值 function fn(n: number = 1) { console.log(n.toFixed()) console.log(n.toFixed(3)) } f() f(10)
回调中的可选参数
-
当为回调写一个函数类型时,永远不要写一个可选参数,除非你打算在不传递该参数的情况下调用函数
function myForEach( arr: any[], callback: (arg: any, index?: number) => void ): void { for (let i = 0; i < arr.length; i++) { callback(arr[i], i) callback(arr[i]) } } myForEach([1, 2, 3], (a) => { console.log(a) }) myForEach([1, 2, 4], (a, i) => { console.log(a, i) }) // 当为回调写一个函数类型时,永远不要写一个可选参数,除非你打算在不传递该参数的情况下调用函数 myForEach([1, 2, 4], (a, i) => { console.log(i?.toFixed()) })
函数重载
基本语法
-
// 重载签名: function makeDate(timestamp: number): Date function makeDate(m: number, d: number, y: number): Date // 实现签名: function makeDate(mOrTimestamp: number, d?: number, y?: number): Date { // ... } // 重载签名: function makeDate(timestamp: number): Date function makeDate(m: number, d: number, y: number): Date // 实现签名: function makeDate(mOrTimestamp: number, d?: number, y?: number): Date { if (d && y) { return new Date(y, mOrTimestamp, d) } else { return new Date(mOrTimestamp) } } const d1 = makeDate(12314213) const d2 = makeDate(5, 6, 7)
重载签名和实现签名
-
3个问题: 参数不正确 参数类型不正确 返回类型不正确function f3n(x: string): void function f3n() {} // 1.参数不正确,必须要传参数 f3n('hello') function f6n(x: boolean): void function f6n(x: string): void // 2.参数类型不正确,必须兼容所有重载函数的入参类型 function f6n(x: boolean | string) {} function f7n(x: string): string function f7n(x: boolean): boolean // 3.返回类型不正确,必须兼容所有重载函数的返回值 function f7n(x: string | boolean): string | boolean { // return 'hello' return true }
编写好的重载
-
在可能的情况下,总是倾向于联合类型的参数而不是重载参数
-
// 不推荐 // function len(s: string): number // function len(arr: any[]): number // function len(x: any) { // return x.length // } // 推荐 应该使用联合类型的参数而不是重载参数 function len(x: any[] | string) { return x.length } len('hello') len([1, 2, 3]) len(Math.random() > 0.5 ? 'hello' : [4, 5, 6])
函数内this的声明
-
interface User { admin: boolean } interface DB { filterUsers(filter: (this: User) => boolean): User[] } const db: DB = { filterUsers: (filter: (this: User) => boolean) => { let user1: User = { admin: true } let user2: User = { admin: false } return [user1, user2] } } // 不能使用箭头函数,不包含this参数 const admins = db.filterUsers(function (this: User) { return this.admin }) console.log(admins)
其他类型(void,object,unknown,never,Function)
void
-
表示没有返回值的函数的返回值;
-
void和undefined不一样
-
function noop() { return; // ts可以推断出返回类型是void }
object
- 指的是任何的**不是基元(string,number,bigint,symbol,boolean,null,undefined)**的值
- object不同于**{ },也不同于Object**
- object不是Object,始终使用object!
unknow
-
代表任何值,与any类似,但是更安全,因为对未知unknow值做的任何事情都是不合法的
-
function f1(a: any) { a.b(); // 正确 } function f2(a: unknown) { a.b(); // 报错 }
never
-
表示永远不会被观察到的值
-
返回值是异常/终止程序执行/死循环的话,返回类型会被推断为never
-
function fail(msg: string): never { throw new Error(msg); // 返回值是异常 } function fn(x: string | number) { if(typeof x === 'string') { // ... } else if(typeof x === 'number') { // ... } else { x; // 'never'类型 } }
Function
-
全局性的Function类型描述了诸如 bind、call、apply 和其他存在于JS中所有函数值的属性
-
还有一个特殊的属性,即Function类型的值总是可以被调用,这些调用返回any
-
推荐使用
() => void方法来定义类型 -
function doSomething(f: Function) { return f(1,2,3) // 返回值是any,不安全,所以不推荐这样做 } // 如果需要接收一个任意的函数,但是不打算调用它,推荐使用以下方法来定义类型 () => void
参数展开运算符
形参展开
-
function multiply(n: number, ...m: number[]): number[] { return m.map((x) => n * x) } const newA = multiply(10, 1, 2, 3, 4) console.log(newA) // [ 10, 20, 30, 40 ]
实参展开
-
const arr1 = [1, 2, 3] const arr2 = [4, 5, 6] arr1.push(...arr2) console.log(arr1) // 因为Math.atan2要求确定的2个参数,应该是不可变的参数,所以要对args使用as断言 const args = [8, 5] as const const angle = Math.atan2(...args) console.log(angle) // 1.0121970114513341
参数解构
-
type ABC = { a: number; b: number; c: number } function sum({ a, b, c }: ABC) { console.log(a + b + c) } sum({ a: 1, b: 2, c: 3 })
返回void类型
-
一个具有void返回类型的上下文函数(type vf = () => void),在实现时,可以返回任何其他的值,但它会被忽略
-
当一个字面的函数定义有一个void的返回类型时,该函数必须不返回任何东西
// 一个具有void返回类型的上下文函数(type vf = () => void),在实现时,可以返回任何其他的值,但它会被忽略 type vf = () => void const f1: vf = () => { return 100 // 可以返回任何类型 } // 当一个字面的函数定义有一个void的返回类型时,该函数必须不返回任何东西 function f4(): void { // return 'a' // 不能返回非void类型 } const f5 = function (): void { // return true // 不能返回非void类型 }
对象
对象类型
-
匿名对象
-
接口命名
-
类型别名
-
// 1.匿名对象,使用对象字面量定义对象类型 function greet(person: {name: string, age: number}) { return 'Hello ' + person.name } // 2.接口命名,使用接口定义对象类型 interface Person2{ name: string age: number } function greet2(person: Person2) { return 'Hello ' + person.name } // 3.类型别名,使用类型别名对象类型 type Person3 = { name: string age: number } function greet3(person: Person3) { return 'Hello ' + person.name }
属性修改器
可选属性
-
type Shape = {} interface PaintOpts { shape: Shape xPos?: number yPos?: number } // 解构后的属性后的alias是shape的别名,并不是类型限制 function paintShape({ shape: alias, xPos = 0, yPos = 0 }: PaintOpts) { console.log(alias, xPos, yPos) } const shape: Shape = {} paintShape({ shape }) paintShape({ shape, xPos: 100 }) paintShape({ shape, xPos: 100, yPos: 100 })
只读属性
-
interface SomeType { readonly prop: string // 只读属性 readonly friends: { name: string } } function doSomething(obj: SomeType) { console.log(obj.prop) // obj.prop = 'hello' // 只读属性(基元类型)无法被修改 obj.friends.name = 'newName' // 只读属性(对象类型)下的属性可以被修改,对象属性本身不能被修改 } // readonly属性可以通过类型别名改变 interface Person { name: string age: number } interface ReadonlyPerson { readonly name: string readonly age: number } let writablePerson: Person = { name: 'aa', age: 18 } // 可以把只读的属性通过可写的属性进行赋值 let readonlyPerson: ReadonlyPerson = writablePerson console.log(readonlyPerson.age) // 18 writablePerson.age++ console.log(readonlyPerson.age) // 19
索引签名
-
不能提前知道一个类型的所有属性名称,但是知道这个值的形状,这种情况下可以使用一个索引签名来描述可能的值的类型
interface StrArr { // 索引签名可以任意的给一个对象定义属性 [index: number]: string } const myArr: StrArr = ['A', 'B'] const secondItem = myArr[0] interface TestStr { [prop: string]: number } const testStr: TestStr = { x: 100, y: 200 } interface Animal { name: string } interface Dog extends Animal { breed: string } interface NotOk { [index: string]: number | string length: number name: string } let notOk: NotOk = { x: 100, length: 12, name: 'aa' } interface ReadonlyStrArr { readonly [index: number]: string } let myA: ReadonlyStrArr = ['a', 'b'] // myA[0] = 'c' // 只读属性不能修改
扩展类型
-
接口允许我们通过扩展其他类型建立新的类型
interface BasicAddress { name?: string street: string city: string country: string } interface Colorful { color: string } // 可以继承多个接口 使用 , 表示继承多个接口 interface AddressWithUnit extends BasicAddress, Colorful { unit: string } let awu: AddressWithUnit = { unit: '3单元', street: '清河街道', city: '北京', country: '中国', name: '', color: 'blue' }
交叉类型
-
用于组合现有的对象类型
interface Colorful { color: string } interface Circle { radius: number } // 可以组合多个接口 使用 & 表示组合多个接口 // 1.通过type定义交叉类型 type ColorfulCircle = Colorful & Circle let cc: ColorfulCircle = { color: 'red', radius: 100 } // 2.通过匿名对象的方法定义交叉类型 function draw(circle: Colorful & Circle) { console.log(circle.color) console.log(circle.radius) } draw({ color: 'red', radius: 100 })
处理冲突(接口与交叉类型)
-
interface Sister { name: string; } interface Sister { age: number; } // 接口可以定义同名的,同名接口会合并,可以实现类型合并 let sister: Sister = { name: 'aaa', age: 22 } // 类型别名无法定义同名类型,可以避免类型冲突 type Brother = { name: string } // type Brother = { // age: number // }
泛型对象类型
-
// 定义对象类型 interface Box { // 1. any,用户可以传入任何类型,失去了定义类型的意义(不推荐) contents: any // 2.unknown, 不能做任何操作 (不推荐),1.使用类型缩小(加判断)2.类型断言 contents2: unknown } let box: Box = { contents: 'hello', contents2: 'hello' } // 3.函数重载 代码冗余,不宜阅读(不推荐) interface NumberBox { contents: number } interface StrBox { contents: string } interface BoolBox { contents: boolean } function setContents(box: StrBox, newContents: string): void function setContents(box: NumberBox, newContents: number): void function setContents(box: BoolBox, newContents: boolean): void function setContents( box: { contents: string | boolean | number }, newContents: boolean | number | string ): void { box.contents = newContents } // 4.泛型对象类型(推荐)灵活,代码量小 interface BoxFin<T> { contents: T } let boxFin: BoxFin<string> = { contents: 'hello' } // 使用type定义新的类型 // 使用interface定义泛型对象类型 interface Box2<T> { contents: T } interface Apple { // ... } let a: Apple = {} type AppleBox = Box2<Apple> let ab: AppleBox = { contents: a } // 使用类型别名定义泛型对象类型 // 不仅可以描述对象的类型,还可以编写其他类型的通用的辅助类型 type Box3<T> = { contents: T } type OrNull<T> = T | null type OneOrMany<T> = T | T[] type OneOrManyOrNull<T> = OrNull<OneOrMany<T>> type OneOrManyOrNullOrString<T> = OneOrManyOrNull<string>
类型操纵
从类型中创建类型
- 用现有的类型或者值来表达一个新的类型的方法
- 泛型类型
- keyof类型操作符
- typeof类型操作符
- 索引访问类型
- 条件类型
- 映射类型
- 模板字面量类型
泛型
使用通用类型变量
-
function loggingIdentity<T>(arg: Array<T>): T[] { console.log(arg.length) return arg } loggingIdentity([1, 2, 3])
泛型类型
-
function identity<T>(arg: T): T { return arg } let myIdentity: <T>(arg: T) => T = identity // 方法 let myIdentity2: { <T>(arg: T): T } = identity // 对象字面量方式 interface GenericIdentityFn { <T>(arg: T): T } let myIdentity3: GenericIdentityFn = identity // 泛型接口方式 interface GenericIdentityFn2<T> { (arg: T): T } let myIdentity4: GenericIdentityFn2<string> = identity // 泛型接口方式 更加严谨
泛型类
-
泛型类的形状和泛型接口类似,在类的名称后面加个<>// 初始化未赋值ts会报错 // 在tsconfig.json中修改为 // "strictPropertyInitialization": false, class GenericNumber<T> { zeroVal: T add: (x:T, y:T) => T } let mygeneric = new GenericNumber<number>() mygeneric.zeroVal = 0 mygeneric.add = function(x, y) { return x+y }
泛型约束
-
interface LengthWise { length: number } function loggingIdenity<T extends LengthWise>(arg: T): T { arg.length return arg } // 可以传入具有lengths属性的值 loggingIdenity('hello') loggingIdenity([1,2,3,])
在泛型中使用类型参数
-
// k类型一定属于T类型的对象类型的某个key function getProperty<T, K extends keyof T>(obj: T, key: K) { return obj[key] } let x = { a: 1, b: 2, c: 3, d: 4 } // 必须传入x中存在的key getProperty(x, 'a') // getProperty(x, 'e') // 报错
在泛型中使用类类型
-
function create<T>(c: { new (): T }): T { return new c() } class Beekeeper { hasMask: boolean = true } class Zookeeper { nametag: string = 'Mikle' } class Animal { numLegs: number = 4 } class Bee extends Animal { keeper: Beekeeper = new Beekeeper() } class Lion extends Animal { keeper: Zookeeper = new Zookeeper() } function createInstance<A extends Animal>(c: new () => A): A{ return new c() } // extends Animal进行约束,所以传入的参数(类)中必须含有numLegs属性 createInstance(Lion).keeper.nametag createInstance(Bee).keeper.hasMask // createInstance(BeeKeeper) // 报错
keyof类型操作符
-
接受一个对象类型,会产生它的key的字符串或者是数字字面量的一个结合,或者是一个联合类型
-
常用于映射类型组合
type Point = { x: number y: number } type P = keyof Point // 只能赋值Point类型中存在的key const p1: P = 'x' const p2: P = 'y' // const p3: P = 'z' // 报错 // 索引签名 type Arr = { [n: number]: unknown } type A = keyof Arr // 只能赋值索引签名(number)类型的值 const a: A = 0 // const b: A = 'b' // 报错 type Mashish = { [k: string]: boolean } type M = keyof Mashish // 只能赋值索引签名(number | string)类型的值 const m1: M = 's' const m2: M = 100 // const m3: M = true // 报错
typeof类型操作符
-
可以在类型的上下文中使用
typeof引用一个变量,或者是属性的类型console.log(typeof 'hello') // string let s = 'hello' let n: typeof s // n的类型为s的类型 n = 'world' // n = 100 // 报错 // ReturnType<T> 获取函数返回值类型 type Predicate = (x: unknown) => boolean type K = ReturnType<Predicate> function f() { const arr = ['1', '2'] return arr.map((x) => Number(x)) } // 只能使用typeof定义类型 type P2 = ReturnType<typeof f> const p3: P2 = [1, 2, 3] function f2() { return { x: 10, y: 10 } } type P3 = ReturnType<typeof f2> const p4: P3 = { x: 1, y: 1 } // 可以用typeof标识标识符,变量以及变量的属性,但不能用来标识类似于函数的结果... function msgBox() {} let shouldContinue: typeof msgBox shouldContinue = () => {}
索引访问类型
-
用来查询另一个类型上特定的属性
type Person = { age: number name: string alive: boolean } type Age = Person['age'] // Person中age的类型 let age: Age = 1 interface Person2 { name: string age: number alive: boolean } type I1 = Person2['age' | 'name'] // Person2中age和name的类型 const i1: I1 = 1 const i2: I1 = 'w' type I2 = Person2[keyof Person2] // 返回Person2中所有key组成的联合类型 const i3: I2 = 1 const i4: I2 = 'w' const i5: I2 = true type AliveOrName = 'alive' | 'name' type I3 = Person2[AliveOrName] // Person2中alive和name的类型 const i6: I3 = 'w' const i7: I3 = true // type I4 = Person2['unAlive'] // 报错,不能定义Person2中未存在的类型 const MyArr = [ { name: 'Mike', age: 15 }, { name: 'Bob', age: 23 }, { name: 'Marry', age: 35 } ] type Person3 = typeof MyArr[number] // 获取MyArr某个元素的类型(age和name的类型) const pp: Person3 = { name: 'Jack', age: 13 } type Age2 = typeof MyArr[number]['age'] // 获取MyArr某个元素的类型(age的类型) const age2: Age2 = 12 type Age3 = Person3['age'] // 获取Person3中age的类型 const age3: Age3 = 33 // 不能使用const定义变量引用,必须使用type const key = 'age' // type Age4 = Person3[key] // 报错 const定义的变量不能作为索引类型使用。 type Age5 = Person3[typeof key] // 1.可以使用typeof获取变量类型作为变量引用 type key = 'age' type Age6 = Person3[key] // 2.可以通过type定义key的类型作为变量引用
条件类型
-
// 用于描述输入和输出之间类型的关系 SomeType extends OtherType ? TrueType : FalseTypeinterface Animal { live(): void } interface Dog extends Animal { woof(): void } type Example1 = Dog extends Animal ? number : string // Example1的类型为number type Example2 = RegExp extends Animal ? number : string // Example的类型为string interface IdLabel { id: number } interface NameLabel { name: string } // 函数重载方式 代码量大 function createLabel(id: number): IdLabel function createLabel(name: string): NameLabel function createLabel(nameOrId: string | number): NameLabel | IdLabel function createLabel(nameOrId: string | number): NameLabel | IdLabel { throw '' } // 使用条件类型 简单 type NameOrId<T extends string | number> = T extends number ? IdLabel : NameLabel function createLabel2<T extends string | number>(nameOrId: T): NameOrId<T> { throw '' } let a1 = createLabel('tabs') // a1的类型为NameLabel let b1 = createLabel(2) // b1的类型为IdLabel let c1 = createLabel(Math.random() > 0.5 ? 'hello' : 2) // c1的类型为 NameLabel | IdLabel
条件类型约束
-
type MessageOf<T> = T extends {message: unknown} ? T['message'] : never;type MsgOf<T extends { message: unknown }> = T['message'] type MessageOf<T> = T extends { message: unknown } ? T['message'] : never interface Email { message: string } type EmailMessage = MessageOf<Email> // 即string类型 let email: EmailMessage = 'hello' interface Dog { bark(): void } type DogMessage = MessageOf<Dog> // 即never类型 let dog: DogMessage = '1' as never type Flatten<T> = T extends any[] ? T[number] : T type Str = Flatten<string[]> // 即string类型 let str: Str = 'a' type Num = Flatten<number> // 即number类型 let num: Num = 1
在条件类型内进行推理
-
// 推断我们在真实分支中使用infer关键字来进行对比的类型 type Flatten<T> = T extends Array<infer Item> ? Item : T;type GetReturnType<T> = T extends (...args: never[]) => infer Return ? Return : never type Num1 = GetReturnType<() => number> // Num1的类型即number let num1: Num1 = 100 type Str2 = GetReturnType<(x: string) => string> // Num1的类型即string let str2: Str2 = 'a' type Bool2 = GetReturnType<(a: boolean) => boolean[]> // Bool2的类型即boolean[] let bool2: Bool2 = [true, false] type Never = GetReturnType<string> // 没有返回值,type不被infer Return约束, Never的类型即never let never: Never = 'a' as never // 当有一个具有多个调用签名类型(比如重载函数),传到infer Return中进行推断的时候 一般从最后一个签名进行推断,不会根据参数类型的列表来重新执行推断的解析 function strOrNum(x: string): number function strOrNum(x: number): string function strOrNum(x: number | string): string | number function strOrNum(x: number | string): string | number { return Math.random() > 0.5 ? 'hello' : 1 } type T1 = ReturnType<typeof strOrNum> // T1的类型即 string | number let t1: T1 = '2' let t2: T1 = 2
分布式条件类型
-
// 当条件类型作用于一个通用类型的时候,给定一个联合类型,其将变成为一个分布式条件类型 type ToArr<T> = T extends any ? T[] : never type StrArrOrNumArr = ToArr<string | number> // 返回一个 string[] | number[] 类型的数组,相当于做了一个分布式的分发 let sa1: StrArrOrNumArr = ['A', 'B'] let sa2: StrArrOrNumArr = [1, 2] let sa3: StrArrOrNumArr = [] type ToArrNotDist<T> = [T] extends [any] ? T[] : never type StrArrOrNumArr2 = ToArrNotDist<string | number> // 返回一个 (string | number)[] 类型的数组,相当于禁止分布式的分发 let sa4: StrArrOrNumArr2 = ['A', 'B', 1] let sa5: StrArrOrNumArr2 = []
映射类型
类
类成员
- 类属性
- readonly
- 构造器
- 方法
- Getters/Setters
- 索引签名
类属性
-
class Point { x y constructor() { this.x = 0 this.y = 0 } } const pt = new Point() pt.x = 1 pt.y = 1 class OkGreeter { name!: string }
readonly
-
class Greeter { readonly name: string = 'init' constructor(name?: string) { if (name) { this.name = name } } err() { // this.name = 'not' // 报错,只读属性无法修改 } } const g = new Greeter() // g.name = 'hello'; // 报错,只读属性无法修改 console.log(g.name);
构造器
-
构造函数不能有类型参数
-
构造函数不能有返回类型注释
class Point1 { x: number y: number constructor(x:number = 0, y: number = 0) { this.x = x this.y = y } } const p = new Point1() console.log(p); class Base { k = 4 } class Derived extends Base { constructor() { super() console.log(this); } } const d = new Derived()
方法
-
class Point2 { x: number = 10 y: number = 10 scale(n: number): void { this.x *= n this.y *= n } } const t = new Point2() t.scale(10) console.log(t) let x: number = 12 class C { x: string = 'hello' m() { this.x = 'world' // this.x是类中实例化后的x x = 120 // 这个x是外部的x } }
Getters/Setters(访问器)
-
如果存在get,但没有set,则该属性自动是只读的
-
如果没有指定setter参数的类型,它将从getter的返回类型中推断出来
-
访问器和设置器必须有相同的成员可见性
-
TS4.3开始,set新值的类型可以和get返回值的类型不同
class C1 { _length = 0 get length() { return this._length } set length(value) { this._length = value } } let c1: C1 = new C1() console.log(c1.length) // 0 c1.length = 100 console.log(c1.length) // 100 class Thing { _size = 0 get size(): number { return this._size } set size(value: number | string | boolean) { let num = Number(value) if (!Number.isFinite(num)) { this._size = 0 return } this._size = num } } let t1: Thing = new Thing() console.log(t1.size); t1.size = 900 t1.size = 'hello'
索引签名
-
class MyClass { [s: string]: boolean | ((s: string) => boolean) x = true check(s: string) { return this[s] as boolean } }
类继承
implements子句
-
implements实现一个新的类,从父类或者接口实现所有的属性和方法,同时可以重写属性和方法,包含一些新的功能
-
可以实现一个类去继承或者实现一个接口
interface Pingable { ping(): void } class Sonar implements Pingable { ping() { console.log('ping') } } class Ball implements Pingable { ping() {} pong() {} } interface A {} interface B {} class AB implements A, B {} // implements不会改变类的类型 interface Checkable { check(name: string): boolean } class NameCheck implements Checkable { check(s: string) { return s.toLowerCase() === 'ok' } } interface A1 { x: number y?: number } class C2 implements A1 { x = 0 } const c2 = new C2() console.log(c2) // 不存在c2.y属性
extends子句
-
可以实现一个类继承另一个类,被继承的类称之为基类或父类,继承的类称之为子类, 继承完之后,具备父类所有属性和方法,还可以定义自己的一些成员
class Animal { move() { console.log('moving'); } } class Dog extends Animal { woof(times: number) { for (let i = 0; i < times; i++) { console.log('woof!'); } } } const dog = new Dog(); dog.move() dog.woof(3)
重写方法
-
派生类可以覆盖基类的一个字段或者属性
-
使用
super.xxx访问基类的方法 -
TS要求派生类总是他的基类的一个子类型
class Ba { greet() { console.log('greet!') } } class De extends Ba { greet(name?: string) { if (name) { console.log('你好,', name) } else { super.greet() } } } const de = new De() de.greet() // greet! de.greet('zzy') // 你好, zzy const b: Ba = de b.greet() // greet!
初始化顺序
-
基类的字段被初始化
-
基类构造函数运行
-
派生类的字段被初始化
-
派生类构造函数运行
class Base1 { name = 'base1' constructor() { console.log(this.name); // base1 } } class Derived1 extends Base1 { name = 'derived1' constructor() { super() console.log(this.name); // derived1 } } const derived1 = new Derived1();
继承内置类型
-
继承TS内置对象,如Error,Array,Map...
class MsgError extends Error { constructor(m: string) { super(m) // 对于低版本JS环境,可以明确设置原型 Object.setPrototypeOf(this, MsgError.prototype) } sayHello() { return '发现' + this.message } } const me = new MsgError('错误') console.log(me.sayHello()) // 发现错误 const mse = new MsgError('错误!') console.log(mse instanceof MsgError) // true
成员的可见性
public
-
公开的,默认值。任何对象在任何地方都可以访问
class Gerrt { public greet() { console.log('hi'); } sayHello() { this.greet() //当前类中访问 } } class Hello extends Gerrt { constructor() { super(); this.greet() // 子类中访问 } } const g1 = new Gerrt() g1.greet() // 实例中访问 g1.sayHello()
protected
-
受保护的。能在当前类或者子类中进行访问
-
派生类可以暴露基类受保护的成员
class Greet2 { public greet() { console.log(this.getName()) // 当前类中可以访问 } protected getName() { return 'hello' } } class SpecialGreet extends Greet2 { public howdy() { console.log(this.getName()) // 子类中可以访问 } } const g3 = new SpecialGreet() g3.greet() g3.howdy() // g3.getName() // 报错,实例中不能访问 class Ba1 { protected m = 10 } class Derived2 extends Ba1 { public m = 15 // 子类中可以修改父类中某个属性的访问权限 } const d3 = new Derived2() console.log(d3.m) // 15 子类修改父类属性的访问权限之后可以在实例中访问
private
-
私有的。只能在当前类中进行访问
-
TS允许跨实例的私有访问
class Base3 { private x = 0 printX() { console.log(this.x); // 1.当前类中可以访问 } } class Derived3 extends Base3 { showX() { // console.log(this.x); // 报错,2.子类中不能访问 } } const de3 = new Derived3(); // console.log(d.x); // 报错,3.实例中无法访问 // 4.TS允许跨实例的私有访问 class AAA { private x = 10 public sameAs(other: AAA) { return other.x === this.x // 载一个类里可以访问另外一个实例的属性 } }
静态成员
-
不与类的特定实例相关联,通过类的构造函数对象本身访问
-
特殊的静态名称不安全,避免使用:name,length,call等
-
**TS没有静态类的概念,因为我们还有函数和普通对象 **
class MyClass1 { private static x = 0 static printX() { console.log(MyClass1.x) } } // console.log(MyClass1.x); // 报错 私有静态属性只能在类中访问 MyClass1.printX() // 0 // 静态成员可以被继承 class Base4 { // static name = 's' // 报错,特殊的静态名称不安全 static getHello() { return 'hello' } } class Derived4 extends Base4 { myHello = Derived4.getHello() } // TS没有静态类的概念,因为我们还有函数和普通对象 class StaticClass { static doSomething() {} } function doSomething() {} const helper = { doSomething() {} }
类里的static区块
-
class Foo { static #count = 0 get count() { return Foo.#count } // static {} 可以在区块内部访问class内部的#开头的私有变量 static { try { const last = { length: 100 } Foo.#count += last.length } catch (error) {} } } // Foo.#count // 报错,#定义的私有变量无法在外部被访问
泛型类
-
class Box<T> { contents: T constructor(value: T) { this.contents = value } // static defaultVal: T // 报错,静态成员不能使用泛型 } const bb: Box<string> = new Box('hello') // bb为string类型 bb.contents = 'str' const bb2 = new Box<boolean>(true) // bb2为boolean类型 bb2.contents = false const bb3 = new Box(12) // 进行类型推断,bb3为number类型 bb3.contents = 100
类运行时中的this
-
class MyClass2 { name = 'MyClass2' getName() { return this.name } // 箭头函数中的this永远指向当前词法作用域 getName2 = () => { return this.name } // 使用this参数 getName3(this: MyClass2) { return this.name } } const c = new MyClass2() const obj = { name: 'obj', getName: c.getName, getName2: c.getName2, getName3: c.getName3 } const gg = c.getName3 // console.log(gg()); // 报错,类型为“void”的 "this" 上下文不能分配给类型为“MyClass2”的方法的 "this"。 console.log(c.getName()); // MyClass2 console.log(c.getName2()); // MyClass2 console.log(c.getName3()); // MyClass2 console.log(obj.getName()); // obj console.log(obj.getName2()); // MyClass2 console.log(obj.getName3()); // obj
this类型
-
会动态的指向当前类的类型
class Box2 { content: string = '' set(value: string) { this.content = value return this } } class ClearBox extends Box2 { clear() { this.content = '' } } const a = new ClearBox() const bd = a.set('hello') console.log(bd) class NewBox { content: string = '' sameAs(other: this) { return other.content === this.content } } class Der extends NewBox { otherContent: string = '?' } const bas = new NewBox() const der = new Der() // der.sameAs(bas) // 报错,类型“NewBox”的参数不能赋给类型“Der”的参数。类型 "NewBox" 中缺少属性 "otherContent",但类型 "Der" 中需要该属性。
基于类型守卫的this
-
可以在类和接口的方法的返回的位置使用
this is Type,this is是固定写法,Type是某种类型 -
当与类型缩小混合使用时,目标对象的类型将被缩小到指定的Type类型,这种现象被称为基于类型守卫的this
class FileSystemObj { isFile(): this is FileRep { return this instanceof FileRep } isDirectory(): this is DirectoryRep { return this instanceof DirectoryRep } isNetwork(): this is NetworkRep & this { return this.network } constructor(public path: string, private network: boolean) {} } class FileRep extends FileSystemObj { constructor(path: string, public content: string) { super(path, false) } } class DirectoryRep extends FileSystemObj { children: FileSystemObj[] constructor(path: string) { super('', false) this.children = [] } } interface NetworkRep { host: string } const fso: FileSystemObj = new FileRep('foo/bar', 'foo') if (fso.isFile()) { // const fso: FileRep fso.content } else if (fso.isDirectory()) { // const fso: DirectoryRep fso.children } else if (fso.isNetwork()) { // const fso: NetworkRep & FileSystemObj fso.host } class Boxs<T> { value?: T hasVal(): this is { value: T } { return this.value !== undefined } } const boxs = new Boxs() boxs.value = 'hello' if (boxs.hasVal()) { console.log(boxs.value) // hello }
参数属性
-
TS可以将构造函数的参数变成具有相同名称和值的一个属性,这些属性是类属性,称之为参数属性
-
对应方法就是在构造函数的前面加上一些可见性的修饰符(public,private,protected,readonly...),由此产生的参数属性将会得到这些修饰符所修饰的含义
class Params { constructor(public readonly x: number, protected y: number, private z: number) { this.x = x; } } const ps = new Params(100, 300, 400) console.log(ps.x); // ps.y // 报错,受保护属性只能在类和子类中访问 // ps.z // 报错,私有属性只能在类中访问
类表达式
-
类表达式与类的声明类似,但是类表达式不需要名字,可以理解为匿名类
const someClass = class<T> { content: T constructor(content: T) { this.content = content } } const m = new someClass('hello') console.log(m.content) // hello
抽象类和成员
-
TS中的类 方法和字段可以是抽象的,一个抽象的方法或抽象的字段是一个没有提供实现的方法和字段,这些成员必须存在于一个抽象类中,不能直接实例化
-
抽象类的作用是作为子类的基类,实现所有抽象成员
-
当一个类没有任何抽象成员时,被称为具体类
abstract class BaseClass { abstract getName(): string printName() { console.log(this.getName()) } } // const bc = new BaseClass(); // 报错,抽象类无法被实例化,需要子类去继承抽象类 class DerivedClass extends BaseClass { getName () { return 'world' } } const dc = new DerivedClass() dc.getName() dc.printName() // 抽象构造签名 不能new一个抽象类 function greet(ctor: new () => BaseClass) { const instance = new ctor() instance.printName() } greet(DerivedClass)
类之间的关系
-
TS中大部分类结构相同的话可以进行比较
class Po1 { x = 1 y = 1 } class Po2 { x = 0 y = 0 } // 同结构类型(属性类型都相等)的类可以相互替代, const po1: Po1 = new Po2() const po2: Po2 = new Po2() class Per { name: string = '' age: number = 100 } class Employee { name: string = '' age: number = 22 salary: number = 10 } const pp: Per = new Employee() // TS内部进行判断,Employee被视为Per的子类 class Empty { } function fn(x: Empty) { } // 可以把任何参数都当作空类的具体的实现 fn(window) fn({}) fn(fn) fn(100)
模块
认识模块
- 语法:导出和导入的语法
- 模块解析:模块名称(或路径)和磁盘上的文件之间的关系
- 模块输出目标:编译出来的JS是什么样的
ES模块语法
-
maths.ts
export var pi = 3.14 export let squareTwo = 1.41 export const phi = -1.61 export class RandomNumberGenerator {} export function absolute(num: number): number { if (num < 0) { return num * -1 } return num } -
hello.ts
export default function helloWorld() { console.log('helloWorld') } -
app.ts
import hello from './hello' import { pi, phi, absolute } from './maths' hello() // helloWorld console.log(pi); // 3.14 console.log(absolute(phi)); // 1.61
额外的导入语法
-
import { old as new } -
maths.ts
export const pi = 3.14 export default class RandomNumberGenerator {} -
app.ts
import RNGen, { pi as Π } from './maths' console.log(Π) // 3.14 const rng = new RNGen() -
app2.ts
// 使用*导入所有内容 import * as math from './maths' console.log(math.pi) // 3.14 const rng = new math.default() // 使用default找到默认导出的内容
TS特定的ES模块语法
export type导出类型import type导入类型import { type Cat}导入对象,对象中通过type指定类型
ES模块语法与CommonJS行为
-
import fs = require('fs') const code = fs.readFileSync('hello.ts', 'utf-8')
CommonJS语法
-
module
-
module.exports
-
require
-
maths.js
function absoulte(num: number) { if (num < 0) { return num * -1 } return num } // 1.导出方法 module.exports = { pi: 3.14, squareTwo: 1.41, phi: -1.61, absoulte } // 2.导出方法 // exports.absoulte = absoulte -
app.ts
const maths = require('./maths') console.log(maths) // maths: // { // pi: 3.14, // squareTwo: 1.41, // phi: -1.61, // absoulte: [Function: absoulte] // }