Typescrip进阶和总结
前言
本文围绕Typescript的类型层级,type 与 interface、抽象类的区别,infer关键字,
Typescript中内置的一些常用工具类型,Typescript中的逆变和协变,还有其他的一些概念进行补充。
Typescript的类型层级
在Typescript中区分为原始类型,装箱类型和字面量类型。
装箱类型是javascript中提供的String,Number,Boolean,null,undefined,Bignit,Symbol,Object等数据类型。
原始类型是Typescript提供的string,number,boolean,null,undefined,bignit,symbol,object等数据类型,也可以称为拆箱类型。
除了null和undefined之外其他的在javascript中是未定义的。
字面量类型是比如说你给一个变量赋值为’aaa’或者123之类的值那么在Typescript中已经被类型推断为一个字符串类型或者数字类型。
// 如下代码, 'aaaa'字符串字面量类型继承了string原始类型,那么就可以认为string原始类型层级大于'aaaa'字符串字面量类型
type result1 = 'aaaa' extends string ? 1 : 2 // 1
type result2 = string extends 'aaaa' ? 1 : 2 // 2
type result3 = 'aaaaa' extends String ? 1 : 2 // 1
type result4 = String extends 'aaaa' ? 1 : 2 // 2
type result5 = string extends String ? 1 : 2 // 1
type result6 = String extends string ? 1 : 2 // 2
原始类型,装箱类型和字面量类型三者的层级是: 字面量类型 < 对应的原始类型 < 对应的装箱类型 < Object类型
在javascript中,一切皆对象,那么Object就是顶级,一切类型来自Object,但是Object的原型对象是null。
在javascript中创建对象有三种方法: 字面量{},new Object(),Object.create({}),
在Typescript中可以通过let obj : object = {} 来声明一个对象,此处的object的o是小写。
object和Object、{}的区别是:object代表着所有非原始类型的类型,即数组、对象与函数类型,
Object 类型是一个内置的接口,它描述了所有对象都具有的一些属性和方法,
{} 类型是一个空对象类型,它表示没有任何属性的对象(object的字面量类型)。
Typescript是javascript的超集,在javascript原有基础类型上扩展了any,never,unknown,enum等数据类型,但顶级对象依然是object。
代码演示
type result7 = {} extends object ? 1 : 2; // 1
type result8 = object extends {} ? 1 : 2; // 1
type result9 = {} extends Object ? 1 : 2; // 1
type result10 = Object extends {} ? 1 : 2; // 1
type result11 = object extends Object ? 1 : 2; // 1
type result12 = Object extends object ? 1 : 2; // 1
上面的 {} extends object |Object和object |Object extends {} 实际上是两种完全不同的比较方式。{} extends object 和 {} extends Object 意味着, {} 是 object 和 Object 的字面量类型,是从类型信息的层面出发的,即字面量类型在基础类型之上提供了更详细的类型信息。object extends {} 和 Object extends {} 则是从结构化类型系统的比较出发的,即 {} 作为一个一无所有的空对象,几乎可以被视作是所有类型的基类,万物的起源,而 object extends Object 和 Object extends object 这两者的情况就要特殊一些,它们是因为“系统设定”的问题,Object 包含了所有除 Top Type 以外的类型(基础类型、函数类型等),object 包含了所有非原始类型的类型,即数组、对象与函数类型,这就导致了你中有我、我中有你的神奇现象。
在这里,我们暂时只关注从类型信息层面出发的部分,即结论为:原始类型 < 原始类型对应的装箱类型 < Object 类型。
在下面的代码中,string、String、'aaaa’是{},Object的子类型,却不是object的子类型。
// {} 作为一个一无所有的空对象,几乎可以被视作是所有类型的基类,万物的起源
type result13 = string extends object ? 1 : 2 // 2
type result14 = string extends Object ? 1 : 2 // 1
type result15 = 'aaaa' extends object ? 1 : 2 // 2
type result16 = 'aaaa' extends Object ? 1 : 2 // 1
type result17 = String extends object ? 1 : 2 // 1
type result18 = String extends Object ? 1 : 2 // 1
type result19 = string extends {} ? 1 : 2 // 1
type result20 = string extends {} ? 1 : 2 // 1
type result21 = 'aaaa' extends {} ? 1 : 2 // 1
type result22 = 'aaaa' extends {} ? 1 : 2 // 1
type result23 = String extends {} ? 1 : 2 // 1
type result24 = String extends {} ? 1 : 2 // 1
在Typescript中,{},object,Object是互相兼容的,但是声明类型只能用{}或者object,继承或实现只能用Object。
class CustonObj1 implements Object {
}
class CustonObj2 extends Object {
constructor() {
super()
}
}
let obj1: {} = {}
let obj2:object = {}
顶级类型 any unknown any表示任意一种数据类型,unknown 表示未知的数据类型。
type result26 = {} extends any ? 1 : 2 // 1
type result27 = any extends {} ? 1 : 2 // 1 | 2
type result28 = {} extends unknown ? 1 : 2 // 1
type result29 = unknown extends {} ? 1 : 2 // 2
type result30 = any extends unknown ? 1 : 2 // 1
type result31 = unknown extends any ? 1 : 2 // 1
类型链的最底层 never never是字面量的子类型
type result32 = 'bbbbb' extends never ? 1 : 2 // 2
type result33 = never extends 'bbbbb' ? 1 : 2 // 1
type result34 = null extends never ? 1 : 2 // 2
type result35 = undefined extends never ? 1 : 2 // 2
type result36 = never extends null ? 1 : 2 // 1
type result37 = never extends undefined ? 1 : 2 // 1
Typescript中的type 与 interface、抽象类的区别
type 在Typescript作为类型别名存在,就是可以给一个类型重新起个名字。
// 定义基本数据类型
type n = number;
type str = string;
type bool = boolean;
type ns = null;
type u = undefined;
type an = any;
type ne = never;
type un = unknown;
// 使用
type C = n // type C = number
// 定义一个对象
type obj3 = {
name: string
age: string
getName: (arg: string) => string
}
// 定义一个函数
type Func1 = {
(name:string,age:number):any
}
// 定义一个类
type Car = {
count : number,
new (count:number) : void
}
// type和泛型联合使用
type Func2<T,U> = {
name : T,
age : U,
[propName:string]:unknown
}
// type和联合类型结合使用
type D = '1' | 1 | true | null | undefined
// implements
type Func3<T, U> = {
name: T
age: U
}
class Functionals1 implements Func3<string, number> {
name: string = 'aaaa'
age: number = 18
}
// type 类型别名不能继承类
interface 在Typescript中是接口,是对结构类型的契约。
// interface不能定义基本数据类型
// 定义一个对象
interface obj4 {
name: string
age: number
[propName: string]: any
}
// 定义一个函数
interface Func4 {
size:number,
(x:number,y:number):number
}
// 定义一个类
interface Person {
name:string,
new (name:string):void,
}
// 类实现接口 implements
class person implements Person {
name:string = '';
constructor(name:string) {
}
}
class Car{
}
// 接口继承类 extends
interface CarType extends Car {
run():void;
}
抽象类 abstract 抽象类只能被继承不能被实例化
抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。 抽象方法的语法与接口方法相似。 两者都是定义方法签名但不包含方法体。 然而,抽象方法必须包含 abstract关键字并且可以包含访问修饰符。
abstract class Department {
constructor(public name: string) {
}
printName(): void {
console.log('Department name: ' + this.name);
}
abstract printMeeting(): void; // 必须在派生类中实现
}
class AccountingDepartment extends Department {
constructor() {
super('Accounting and Auditing'); // 在派生类的构造函数中必须调用 super()
}
printMeeting(): void {
console.log('The Accounting Department meets each Monday at 10am.');
}
generateReports(): void {
console.log('Generating accounting reports...');
}
}
let department: Department; // 允许创建一个对抽象类型的引用
department = new Department(); // 错误: 不能创建一个抽象类的实例
department = new AccountingDepartment(); // 允许对一个抽象子类进行实例化和赋值
department.printName();
department.printMeeting();
department.generateReports(); // 错误: 方法在声明的抽象类中不存在
Typescript中的infer关键字 TypeScript 中支持通过 infer 关键字来在条件类型中提取类型的某一部分信息。
type Func = (...args:any[]) => any[]
type functional<T extends Func> = T extends (...args:any[]) => infer R ? R : never
type functionals = functional<Func> // any[]
infer 其实就是一个占位符。
type Swap<T extends any[]> = T extends [infer A, infer B] ? [B,A] : T;
type SwapResult1 = Swap<['1',2]> // [2.'1']
type SwapResult2 = Swap<['1',2,true]> // ['1',2,true]
type ArrayItemType<T> = T extends Array<infer ElementType> ? ElementType : T
type Result1 = ArrayItemType<['aaa', 1, { name: 'bbbb' }, true, null, undefined]> // true | 1 | {name: 'bbbb';} | "aaa" | null | undefined
type Result2 = ArrayItemType<string> // string
type FunctionType = (...args: any[]) => any
type FunctionParamster<T extends FunctionType> = T extends (arg: infer P, ...args: any) => any ? P : never
type FunctionParamster1 = FunctionParamster<(arg: number) => number> // type FunctionParamster1 = number
type FunctionParamster2 = FunctionParamster<(...args: any[]) => any> // type FunctionParamster2 = any
type FunctionParamster3 = FunctionParamster<(args: string) => string> // type FunctionParamster3 = string
type FunctionParamster4 = FunctionParamster<() => void> // type FunctionParamster4 = unknown
Typescript的内置工具类型
type Person = {
name : string,
age:number,
sex?: string
}
type User = Partial<Person> // 将所有属性变为可选属性
// type User = {
// name?: string | undefined;
// age?: number | undefined;
// sex?: string | undefined;
// }
type User = Required<Person> // 将所有属性变为必传属性
// type User = {
// name: string;
// age: number;
// sex: string;
// }
type ReadonlyUser = Readonly<Person> // 将所有属性变为只读属性
// type ReadonlyUser = {
// readonly name: string;
// readonly age: number;
// readonly sex?: string | undefined;
// }
type Name = Pick<Person,'name'> // 获取接口中的某个属性
// type Name = {
// name: string;
// }
type NotName = Omit<Person,'name'> // 去掉接口中的某个属性
// type NotName = {
// age: number;
// sex?: string | undefined;
// }
type Data = Record<string,string> // 定义一个string类型的索引签名,返回值是string类型的接口
// type Data = {
// [x: string]: string;
// }
type AExtractB = Extract<string|number,undefined|string | number> //类型交集
// type AExtractB = string | number
type A = 1 | 2 | true
type B = null | undefined | true
type AExcludeB = Exclude<A,B> //类型差集
// type AExcludeB = 1 | 2 以第一个传入的接口类型为准,找A里面不含有B的
Typescript中的逆变和协变
class Animal {
asPset():void {
}
}
class Dog extends Animal {
dark():void {
}
}
class Corgi extends Animal {
cute():void {
}
}
// 参数类型允许为Dog的父类型,不能为Dog的子类型。
// 返回值允许为Dog的子类型,不能为Dog的父类型
type DogFactory = (args:Animal)=> Dog
function transformDogAndCorgi(dogFactory:DogFactory) {
const dog = dogFactory(new Dog())
dog.dark()
}
function markDogDark(dog:Dog) {
dog.dark()
}
函数类型的参数类型使用子类型逆变的方式确定是否成立,返回值使用子类型协变的方式确定是否成立。
对象的索引类型查询和访问
interface Foo1 {
name: 'aaa'
age: 18
}
type FooKeys = keyof Foo1 & {} // type FooKeys = "name" | "age"
type KeyTypes = Foo1[FooKeys] //type KeyTypes = "aaa" | 18
类型查询操作符 typeof
const func = (input: string) => {
return input.length > 10;
}
const func2: typeof func = (name: string) => {
return name === 'jian'
}
const func = (input: string) => {
return input.length > 10;
}
// boolean
type FuncReturnType = ReturnType<typeof func>;
结尾
关于本文有讲的不到位的或者错误的地方请指正,谢谢大家。后续有空会继续更新Typescript相关知识。