#TypeScript高级类型
1. 概述
TS中的高级类型很多,重点学习以下类型:
- class类
- 类型兼容性
- 交叉类型
- 泛型和keyof
- 索引签名类型和索引查询类型
- 映射类型
2. class类
TS全面支持ES中的class关键字,并为其添加类型注解和其他语法
class基本使用如下:
class Person{
}
const p = new Person()
2.1 构造器
1. 构造器是用来初始化类的实例的。类的成员必须初始化之后,才可以通过 this.成员名称 来访问实例成员 2. 需要为构造函数指定类型注解,否则会被隐式推断为 any ;构造函数不需要返回值类型
class Person{
age: number;
name: string;
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
}
const person = new Person('wjw',18)
注意:TS不允许一个类中有多个构造器(JAVA里面是可以的)
2.2 实例方法
类里面的实例方法的类型注解(参数和返回值)与函数用法相同
class Animal{
public name: string = 'helloworld';
public age: number = 0;
public drink(water:string){
console.log(water);
}
}
new Animal().drink("可口可乐")
2.3 类的继承
类继承的两种方式:1. extends(继承父类) 2. implements(实现接口) 子类继承父类,子类就有了父类和子类的所有属性和方法
class Animal{
public name: string = 'helloworld';
public age: number = 0;
public drink(water:string){
console.log(water);
}
}
class Dog extends Animal{ //extends继承父类
public eat(food:string){
console.log(`food: ${food}`);
}
}
let dog = new Dog()
dog.eat('热狗')
dog.drink('可乐')
interface IAnimal{
sing():void
}
class Animal2 implements IAnimal{
sing(): void {
console.log('animal implement IAnimal');
}
}
new Animal2().sing()
2.4 类成员的可见性
可以使用TS来控制class的方法或属性对于class外的代码是否可见 可见性修饰符包括:1. public(公有的) 2. protected(受保护的) 3. private(私有的)
解释:
- 在类属性或方法前面添加public关键字,来修饰该属性或方法是共有的
- 因为public是默认可见性,所以,可以直接省略
- protected: 表示受保护的,仅对于其申明所在类和子类中可见,但是子类的实例对象是不可见的
class Animal{
public name: string = 'helloworld';
public age: number = 0;
protected drink(water:string){
console.log(water);
}
}
new Animal().drink("可口可乐") //报错,属性“drink”受保护,只能在类“Animal”及其子类中访问,不能在实例对象中使用
- private:表示私有的,只在当前类可见,对实例对象以及子类都是不可见的
| 范围 | public | protected | private |
|---|---|---|---|
| 当前类 | 可见 | 可见 | 可见 |
| 子类 | 可见 | 可见 | 不可见 |
| 当前类实例对象 | 可见 | 不可见 | 不可见 |
| 子类实例对象 | 可见 | 不可见 | 不可见 |
- 只读修饰符: 表示只读,用来防止在构造函数中外对属性进行赋值,并且只能修饰属性不能修饰方法。在接口中也可以使用readonly
class Person{
readonly name: string = 'wjw'
constructor(name: string){
this.name=name
}
}
interface ITest{
readonly name: string
}
let obj: ITest ={
name: "jack"
}
obj.name='mary' //报错,无法为“name”赋值,因为它是只读属性
3. 类型兼容性
TS是结构化类型系统,对于结构化类型系统,如果两个对象具有相同的形状,则认为他们属于同一类型
上述说法其实并不准确,更准确的说法:对于对象类型来说,y的成员至少与x相同,则x兼容y(成员多的可以赋值给成员少的),也就是超集的关系可以进行兼容赋值
class Point{
x:number;
y:number;
}
class Point3D{
x:number;
y:number;b
z:number;
}
const p:Point = new Point3D();//只有Pointe3D能满足Point类型要求的,那么就可以说Point兼容Point3D
Point3D的成员至少与Point相同,则Point兼容Point3D。多一个属性就多一个约束,小狗是动物吗?是!动物是小狗吗?不是
4. 函数兼容性
函数的兼容性比较复杂,需要考虑:1. 参数个数 2. 参数类型 3. 返回值类型
- 参数个数:参数个数多的兼容参数个数少的(参数少的可以赋值给多的)
type F1=(x:number) => void
type F2 = (x:number, y:number, z:number) => void
let f1:F1 = (x)=>console.log(x);
let f2:F2 = f1 //参数少的可以赋值给参数多的
- 参数类型:相同位置的参数类型要相同(原始类型)或者类型要兼容(对象类型)
class Father {
name:string
age:number
}
class Son {
name:string
age:number
address:string
}
type F3 = (p:Father) => void
type F4 = (p:Son) => void
let f3!:F3
let f4!:F4
// f3=f4 //不能将类型“F4”分配给类型“F3”。
//参数“p”和“p” 的类型不兼容。
//类型 "Father" 中缺少属性 "address",但类型 "Son" 中需要该属性
判断函数兼容技巧:将对象拆开,把每个属性看作一个个参数,参数少的可以赋值给参数多的
- 返回值类型,只关注返回值类型本身即可
解释:
- 如果返回值类型是原始类型,此时两个类型要相同。
- 如果返回值类型是对象类型,此时成员多的可以赋值给成员少的,因为成员多的里面包含了成员少的
5. 类型守卫
- typeof 原始类型保护
- instanceof class类型保护
- in 类型保护
- 自定义类型保护
注意:typeof只能给出原始类型(如 number、string、object 等),而不能给出接口或类的类型
type FunB=(a:number|string)=>void
let funTypeof:FunB=(a)=>{
if(typeof a=='string'){
console.log('funTypeof is string');
}
else if(typeof a=='number'){
console.log('funTypeof is number');
}
}
funTypeof(123) //funTypeof is number
funTypeof('123') //funTypeof is string
class IA{
IAdd(i: number):void{
}
}
class IB{
IBdd(num:number):void{
}
}
type FunA=(arg:IA|IB)=>void
let funInstanceof:FunA =(args)=>{
if(args instanceof IA){
args.IAdd(1);
}else if(args instanceof IB){
args.IBdd(1);
}
}
class C{
name: string='wjw'
age: number=12
}
class D{
address: string='xian'
age: number=10
}
function functionIn(args: C|D){
if('name' in args){
console.log("name in C");
}
else if('address' in args){
console.log("address in D");
}
}
functionIn(new C())
6. 交叉类型
将多个类型合并为一个类型 符号:&
class A{
name: string
}
class B{
age: number
}
let stu:A&B={ //类的交叉
name:'wjw',
age: 10
}
interface IA{
name: string
}
interface IB{
age:number
sayHi(): void
}
let stu2:IA&IB={ //接口的交叉
name:'wjw',
age: 10,
sayHi(){
}
}
7.联合类型
定义:声明的类型不确定 语法:|
//基本联合类型
let a:number|string = 10;
let size: 'BIG'|'SMALL'|'MIDDLE'='BIG'
//可辨式联合类型
interface Square{
kind:'正方形',
size: number
}
interface Rectangle{
kind:'长方形',
width: number,
height: number
}
interface Circle{
kind:'圆形',
radius:number
}
type Shape = Square|Rectangle|Circle
function getArea(shape:Shape){
switch(shape.kind){
case '正方形':{
return shape.size*shape.size
}
case '长方形':{
return shape.width*shape.height
}
case '圆形':{
return 3.14*shape.radius*shape.radius
}
default:
throw new Error("Shape Error")
}
}
console.log("面积是:"+getArea({kind:'长方形',width: 5,height: 10}));
8. 索引操作符
- 索引查询操作符:keyof T
- 表示提取T类型的所有公共属性,组成字面量联合类型
- 索引访问操作符:T[k]
- 表示对象T的属性K声明的类型 keyof是为了取得对象的key值组成的联合类型
//查询操作符
interface Person{
name: string;
age: number;
sayHi():void
}
let k: keyof Person;
k='age' //k可以是Person里面的任意一个属性名:'name'|'age'|'sayHi'
k='sayHi'
//访问操作符 T[K]
let k2:Person['name'] //k2的类型就是Person类里面name属性的类型 : string
9. 泛型约束extends
使用extends关键字来约束泛型的类型范围,extends后面可以是原始类型、类、接口、联合类型、交叉类型
//约束为原始类型
function F1 <T extends any>(x:T){
console.log(typeof x);
}
F1(123) //number
//约束为对象类型,要求泛型所代表的类型里面必须含有C1类的所有属性
class C1{
length:number=10;
}
function F2<T extends C1>(x:T){
console.log(x.length);
}
F2({length:12})
//约束对象为接口类型,要求泛型所代表的类型满足接口I1
interface I1{
length:number;
}
function F3<T extends I1>(x:T){
console.log(x.length);
}
F3({length:20})
//约束类型为联合类型,要求泛型所代表的类型不是number,就是string
type T1 = number|string
function F4<T extends T1>(x:T[]){
x.map((item)=>{
console.log(item);
})
}
F4([1,'2',3,'4'])
10. 索引签名类型
绝大多数情况下,我们都可以在使用对象前就确定对象的结构,并且为对象添加准确的类型.但是当我们无法确定对象中有哪些属性(或者说无法确定对象中可以出现任意多个属性),此时,就用到索引签名类型了
解释:
- 使用 [key:string] 来约束该接口中允许出现的属性名称。表示只要是string类型的属性名称,都可以出现在对象中
- 这样,对象obj中就可以出现任意多个属性(比如a、b等)
- key只是一个占位符,可以换成任意合法的变量名称
- JS中对象的键key是string类型的
interface AnyObject{
[key: string]:number
}
let obj:AnyObject = {
a:1,
b:2,
}
在JS中数组是一类特殊的对象,特殊在数组的键(索引)是数值类型,并且,数组也可以出现任意多个元素,所以,在数组对应的泛型接口中,也用到了索引签名类型
interface MyArray<T>{
[n: number]:T
}
let arr:MyArray<number> = [1,3,56]
11. 映射类型
映射类型:基于旧类型创建新的类型(对象类型),减少重复,提升开发效率 比如,类型PropsKeys有x/y/z,另外一个类型Type1中也有x/y/z,并且Type1中的x/y/z的类型相同:
type PropsKeys = 'x'|'y'|'z'
type Type1 = {x:number,y:number,z:number},
这样书写没错,但是x/y/z重复书写了两次,像这种情况,就可以使用映射类型来进行简化
type PropsKeys = 'x'|'y'|'z'
type Type2 = { [Key in PropsKeys]:number }
//等价于
type Type2 = {x: number, y: number,z: number}
解释:
- 映射类型是基于索引签名类型的,所以,该语法类似于索引签名类型,也使用了[]
- Key in PropsKeys 表示Key可以是PropsKeys联合类型中的任意一个,类似于forin(let key in obj)
- 使用映射类型创建的相对性类型Type2和Type1结构完全相同
映射类型不能在接口中使用,只能在type中使用
映射类型联合keyof使用
type Props = {a:number, b:number, c:string, d:number}
type Type3 = {[key in keyof Props]:number}