基本类型
-
类型声明:通过类型声明可以指定ts中变量(参数、形参)的类型,指定类型后,当为变量赋值时,ts编辑器会自动检查值是否符合类型声明,不符合就报错;
-
如果变量的声明和赋值是同时进行的,ts可以自动对变量进行类型检查;
let c: boolean = false; // :boolean 就是类型注解 let x = true; //自动检查x变量为布尔值 x = 3; //会报错
function sum(a:number, b:number): number{ //限制参数和返回值的类型 return a + b; } sum(123,'456',789); //第二个参数报错,传递的应该是一个数字而非字符串; 第三个参数报错,因为函数限制了两个
-
类型:
类型 描述 number 任意数字 string 任意字符串 boolean 布尔值 字面量 限制变量的值就是该字面量的值 any 任意类型 unknown1 类型安全的any void 没有值(或者undefined) never 不能是任何值 object 任意的js对象 array 任意的js数组 tuple 元组,固定长度数组 enum 枚举类型 -
语法
let 变量: 类型 let 变量:类型 = 值 function fn(参数:类型,参数:类型):类型{ ... }
-
联合类型
let c: boolean | string; c = true; c = 'hello';
-
any类型 ,相当于对该变量关闭了ts校验,不建议使用;声明变量如果不指定类型,则ts解析器会自动判断变量的类型为any,避免使用;如果类型不确定可以用any,但还有一个解决方法,用unknown;
let e: unkown; let s:string; e = 'hello' // unkown 实际上就是一个类型安全的any // unkown 类型的变量,不能直接赋值给其他变量 if(typeof e === 'string'){ s = e; } // 类型断言,可以用来告诉解析器变量的实际类型 /** 语法: 变量 as 类型 <类型>变量 */ s = e as string; s = <string>e;
-
object 类型,一般不去限制是不是一个对象,而是去限制对象里的属性;
let b: {name:string,age?:number}; //在属性名后加上一个?,表示是可选属性,可有可无 b = {name: '孙悟空'}; let c: {name:string,[propName: string]: any}; //如果只想限制name,其他可有可无,则可以用[propName:string]:any 如果想限制值的类型,可把any换成其他类型4 c = {name:'猪八戒',age:18, gender:'man'}
-
数组
语法: 数组的类型声明: 类型[] Array<类型>
let e: string[]; //string[] 表示字符串数组 e = ['a','b',1]; //第三个参数1飘红报错,因为限制了数组里的值只能是字符串 let g: Array<number>; //表示数值数组,限制值只能为数值
-
元组tuple :就是固定长度的数组
语法:[类型,类型,类型]
let h: [string,string] h = ['nihao'] //飘红,因为需要两个,但这块只传了一个
-
enum 枚举
enum Gender{ Male = 0; Female = 1; } let i: {name: string, gender: Gender} i = { name: '孙悟空', gender:Gender.Male //'male' } console.log(i.gender === Gender.Male)
-
类型的别名
type myStr = string let s:myStr //等同let s: string let myType = 1 | 2 | 3 | 4 | 5 let k: myTyppe; let l: myType
运算符
非空断言运算符 !
用在变量或函数名之后,用来强调对应的元素是非null | undefined的,这个符号的场景,特别适用于我们已经明确知道不会返回空值的场景,从而减少冗余的代码判断
可选链运算符 ?.
?.用来判断左侧的表达式是否是 null | undefined,如果是则会停止表达式运行,可以减少我们大量的&&运算。
空值合并运算符 ??
??与||的功能是相似的,区别在于 ??在左侧表达式结果为 null 或者 undefined 时,才会返回右侧表达式 。
数字分隔符_
_可以用来对长数字做任意的分隔,主要设计是为了便于数字的阅读,编译出来的代码是没有下划线的,请放心食用。
操作符
keyof是索引类型查询操作符
keyof
可以获取一个类型所有键值,返回一个联合类型
type Person {
name: string,
age: number
}
type PersonKey = keyof Person // PersonKey得到的类型为 'name' | 'age'
keyof
的一个典型用途是限制访问对象的 key
合法化,因为 any
做索引是不被接受的。
function getValue (p: Person, k: keyof Person){
return p[k] // 如果k不如此定义,则无法以p[k]的代码格式通过编译
}
总结起来 keyof
的语法格式如下
类型 = keyof 类型
实例类型获取typeof
typeof
是获取一个对象/实例的类型,如下:
const me: Person = {name: 'healer', age:16};
type P = typeof me; // { name: string, age: number | undefined }
const you: typeof me = {name: 'mama',age: 69} // 可以通过编译
typeof
只能用在具体的对象上,这与 js
中的 typeof
是一致的,并且它会根据左侧值自动决定应该执行哪种行为。
const typestr = typeof me; // typestr的值为"object"
typeof
可以和 keyof
一起使用(因为 typeof
是返回一个类型嘛),如下
type PersonKey = keyof typeof me; // 'name' | 'age'
总结起来 typeof
的语法格式如下:
类型 = typeof 实例对象
遍历属性in
in
只能用在类型的定义中,可以对枚举类型进行遍历,如下:
// 这个类型可以将任何类型的键值转化成number类型
type TypeToNumber<T> = {
[key in keyof T]: number
}
keyof
返回泛型 T 的所有键枚举类型,key
是自定义的任何变量名,中间用in
链接,外围用[]
包裹起来(这个是固定搭配),冒号右侧number
将所有的key
定义为number
类型。
于是可以这样使用了:
const obj: TypeToNumver<Person> = {name: 10, age: 10}
总结起来 in 的语法格式如下:
[ 自定义变量名 in 枚举类型 ]: 类型
面向对象
万物皆对象: 属性 + 方法
-
类
class 类名{ 属性名: 类型; constructor(参数:类型){ this.属性名 = 参数; } 方法名(){ ... } }
class Person{ // 实例属性 - 通过对象访问 name: string age: number //静态属性 - 通过类访问 static stic: number = 18; //构造函数 - new一个对象的时候执行 constructor(name: string, age: number){ this.dog = dog this.dogAge = age } //方法,一般放原型上 bark(){ console.log('汪汪汪') } } const per = new Person()
-
继承 - 抽取公共代码
使用
extends
关键字继承父类的属性和方法使用
super
关键字class Dog extends Animal{ sayHello(){ super.sayHello() //调用父类的方法 } }
constructor(name:string, age: number){ super(name) // 调用父类的构造函数 this.age = age }
-
抽象类
比如说Animal作为一个基类,而它的子类Dog才是真正用来创建对象的,如果说不希望使用Animal去创建对象,那么在基类前加
abstract
关键字抽象类就是专门用来被继承的类,不能创建实例对象
抽象类可以用来创建对象方法,抽象方法使用
abstract
关键字开头,没有方法它,,返回void
,抽象方法只能定义在抽象类中,子类必须对抽象方法进行重写
接口
- 接口用来定义一个类结构 关键字interface
// 描述一个对象的类型 type myType = { name: string, age: number } // 描述用来定义一个类结构 interface myInterface{ name: string; age: number; sayHello():void }
- 接口可重复声明,它会合并
- 定义类时,可以使用类去实现一个接口,实现接口就是使类满足接口的要求
class MyClass implements myInter{ name: string; constructor(name: string){ this.name = name } sayHello(){ console.log('你好') } }
属性的封装
ts在属性前添加属性的修饰符
public
公有属性 (直接访问,可读可修改)private
私有属性 (只有在类里面访问,外面访问不到,不能直接获取属性,可以定义一个方法用来访问)protect
保护属性(只能在当前类和子类中访问,外部不能访问)
泛型
定义函数或者类时,如果遇到类型不明确时,可以使用泛型
// T表示任意类型,只有函数执行时才知道时什么类型。T也可以叫其他名字,任取
function fn<T>(a:T):T
let res1 = fn(a:10); //不指定泛型,ts会自动推断
let res2 = fn<string>(a:'hello') //指定泛型
// 泛型还可以同时指定多个
function fn2<T,K>(a: T, b:K):T{
console.log(b);
return a;
}
fn2<number,string>(a:123, b:'hello')
interfave Inter{
length: number
}
//T extends Inter 表示泛型T必须是Inter实现类(子类)
function fn3<T extends Inter>(a: T):number{
return a.length
}
//泛型类
class MyClass<T>{
name: T;
constructor(name:T){
this.name = name;
}
}
const mc = new MyClass<string>{name:'孙悟空'}
泛型在 TS 中可以说是一个非常重要的属性,它承载了从静态定义到动态调用的桥梁,同时也是 TS 对自己类型定义的元编程。泛型可以说是 TS 类型工具的精髓所在
一 . 基本使用
分普通类型定义,类定义,函数定义
// 普通类型定义
type Dog<T> = { name: string, type: T}
// 普通类型使用
const dog: Dog<number> = { name:'ww', type:20}
// 类定义
class Cat<T> = {
private type:T,
constructor(type:T){this.type = type}
}
// 类使用
const cat:Cat<number> = new Cat<number>(20) //或简写 const cat = new Cat(20)
// 函数定义
function swipe<T,U>(value:[T,U]):[U,T]{
return [value[1],value[2]];
}
// 函数使用
swipe<Cat<number>,Dog<number>>([cat,dog]) // 或简写 swipe([cat, dog])
注意,如果对一个类型名定义了泛型,那么使用此类型名的时候一定要把泛型类型也写上去。
而对于变量来说,它的类型可以在调用时推断出来的话,就可以省略泛型书写。
泛型的语法格式简单总结如下:
类型名<泛型列表> 具体类型定义
二. 泛型推导与默认值
我们可以简化对泛型类型定义的书写,因为TS会自动根据变量定义时的类型推导出变量类型,这一般是发生在函数调用的场合的。
type Dog<T> = { name: string, type: T }
function adopt<T>(dog: Dog<T>) { return dog };
const dog = { name: 'ww', type: 'hsq' }; // 这里按照Dog类型的定义一个type为string的对象
adopt(dog); // Pass: 函数会根据入参类型推断出type为string
若不适用函数泛型推导,我们若需要定义变量类型则必须指定泛型类型。
const dog: Dog<string> = { name: 'ww', type: 'hsq' } // 不可省略<string>这部分
如果我们想不指定,可以使用泛型默认值
的方案。
type Dog<T = any> = { name: string, type: T }
const dog: Dog = { name: 'ww', type: 'hsq' }
dog.type = 123; // 不过这样type类型就是any了,无法自动推导出来,失去了泛型的意义
泛型默认值的语法格式简单总结如下:
泛型名 = 默认类型
泛型约束
有的时候,我们可以不用关注泛型具体的类型,如:
function fill<T>(length: number, value: T): T[]{
return new Array(length).fill(value)
}
这个函数接受一个长度参数和默认值,结果就是生成使用默认值填充好对应个数的数组。我们不用对传入的参数做判断,直接填充就行了,但是有时候,我们需要限定类型,这时候使用extends
关键字即可。
function sum<T extends number>(value: T[]):number{
let count = 0
value.forEach(v => count += v)
return count
}
这样你就可以以sum([1,2,3])这种方式调用求和函数,而像sum([‘1’, ‘2’])这种是无法通过编译的。
泛型约束也可以用在多个泛型参数的情况。
function pick<T, U extends keyof T>(){}
这里的意思是限制了 U 一定是 T 的 key 类型中的子集,这种用法常常出现在一些泛型工具库中。
extends 的语法格式简单总结如下,注意下面的类型既可以是一般意义上的类型也可以是泛型。
泛型名 extends 类型
泛型条件
上面提到 extends
,其实也可以当做一个三元运算符,如下:
T extends U? X: Y
这里便不限制 T 一定要是 U 的子类型,如果是 U 子类型,则将 T 定义为 X 类型,否则定义为 Y 类型。分配式结果
所以,extends 的语法格式可以扩展为
泛型名A extends 类型B ? 类型C: 类型D
泛型推断 infer
infer
的中文是“推断”的意思,一般是搭配上面的泛型条件语句使用的,所谓推断,就是你不用预先指定在泛型列表中,在运行时会自动判断,不过你得先预定义好整体的结构。举个例子
type Foo<T> = T extends {t: infer Test} ? Test: string
首先看extends后的内容,{t: infer Test}
可以看成是一个包含t属性的类型定义,这个t
属性的value类型通过infer进行推断后会赋值给Test类型,如果泛型实际参数符合{t: infer Test}
的定义那么返回的就是Test
类型,否则默认给缺省的string
类型。
举个例子加深下理解:
type One = Foo<number> // string,因为number不是一个包含t的对象类型
type Two = Foo<{t: boolean}> // boolean,因为泛型参数匹配上了,使用了infer对应的type
type Three = Foo<{a: number, t: () => void}> // () => void,泛型定义是参数的子集,同样适配
infer
用来对满足的泛型类型进行子类型的抽取,有很多高级的泛型工具也巧妙的使用了这个方法。