上一篇文章简单介绍了TS的基本类型和常用类型,这一篇文章讲介绍相对高级一点的类型 枚举
、type
、交叉类型
、映射类型
、类
、泛型
,由于TS内容确实很多,本篇案例首先会尽可能精简,其次有些内容比较好理解将不再给案例,力求这几篇文章覆盖TS绝大部分知识点,绝不留下面试盲区或技术漏洞😂。
type
和inteface
有什么异同?TypeScript
类中成员的 public
、private
、protected
修饰符的理解?带着问题开始本篇文章的阅读吧
我个人理解 字面量类型是联合类型的一种特殊情况,联合类型可以联合基本类型和非基本类型,而字面量类型只有三种
tslet color: "red" | "green" | "blue"; // 字符串字面量类型
let number: 1 | 2 | 3; // 数字字面量类型
let bool: true; // 布尔字面量类型
color = "red"; // 可以赋值为 "red"、"green" 或 "blue"
// 错误示例,因为类型不匹配
color = "yellow"; // 类型错误,只能赋值为 "red"、"green" 或 "blue"
// 字面量类型与联合类型之间的关系
type Colors = "red" | "green" | "blue"; // 字面量类型
type UniType = "red" | "green" | "blue" | "yellow"; // 联合类型
function getColor(colors: Colors, uniType: UniType) {
// colors = uniType; // 报错 不能将类型“"yellow"”分配给类型“Colors”。ts(2322)
uniType = colors; // 可以因为后者是前者的子集
}
枚举(Enum
)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。
enum
来定义 enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
0
开始+1
递增, 同时会对枚举值到枚举名进行反向映射ts// 使用枚举值中其他枚举成员
enum Message {
Error = "error message",
ServerError = Error,
}
console.log(Message.Error); // 'error message'
tsenum Color {Red, Green, Blue = "blue".length};
// 如果紧接在计算所得项后面的是未手动赋值的项,那么它就会因为无法获得初始值而报错:
enum Color {Red = "red".length, Green, Blue};
// index.ts(1,33): error TS1061: Enum member must have initializer.
常数枚举是使用const enum
定义的枚举类型
外部枚举是使用declare enum
定义的枚举类型
接口类型的作用就是将内联类型抽离出来,从而实现类型复用。其实,还可以使用类型别名接收抽离出来的内联类型实现复用。
type Name = number | string;
type Vegetables = {radius: number} & {length: number}
相同点:
interface
通过 extends
或implements
实现继承 (后面讲继承)type
通过交叉类型进行继承,左右有些区别,type
通过extends
编程更方便 (条件及递归)
不同点interface
可以重复声明type
是别名, 原始类型变更会跟着同步变更type
可以用于基本类型、联合类型、交叉类型、元组等需要手写的地方交叉类型是将多个类型合并为一个类型
tsinterface FirstType {
prop1: number;
prop2: string;
}
interface SecondType {
prop2: number;
prop3: boolean;
}
type CombinedType = FirstType & SecondType;
const obj: CombinedType = {
prop2: throwError('--'),
prop1: 42,
prop3: false,
};
function throwError(message: string): never {
throw new Error(message);
}
// 合并联合类型 --> 交集
type A = "blue" | "red" ;
type B = 996 | 'red';
type C = A & B // C: 'red'
// 可选 只读 必选属性交叉的结果
type AA = InsertToObj<{name?: string} & {name:string}> // 得到 AA: {name:string}
type BB = InsertToObj<{readonly name?: string} & {name:string}> // 得到 BB: {name:string}
type CC = InsertToObj<{readonly name: string} & {name?:string}> // 得到 CC: {name:string}
type DD = InsertToObj<{readonly name: string} & {readonly name?:string}> // 得到 DD: { readonly name:string}
实际上,经常会把对象当 Map 映射使用。比如枚举对象的key类型
tsinterface Person {
name: string;
age: number;
}
type Keys = keyof Person; // Keys: 'name' | 'age'
如果像要获取对象的值类型怎么办呢?
tstype ValueType = Person[keyof Person] // ValueType: string | number
需要注意,当使用数字作为对象索引时,它的类型既可以与数字兼容,也可以与字符串兼容,这与 JavaScript
的行为一致。因此,使用 0
或 '0'
索引对象时,这两者等价。
tstype Student = [string, number]
type Values = Student[number]; // Values: string | number
interface RoleDic {
[id: number]: string;
}
const role1: RoleDic = {
0: "super_admin",
1: "admin"
};
const role3: RoleDic = ["super_admin", "admin"]; // ok
role3.length // 报错,因为role3 并非真的数组
索引类型是获取对象map
的key
或value
得到一个联合类型,而映射类型通常借助索引类型生成一种新的类型。
tsreadonly Person --> PersonReadonly --> Person
interface Person {
name: string;
}
type MyReadonly<T> = {
readonly [P in keyof T]: T[P];
}
type MyReadonlyRemove<T> = {
-readonly [P in keyof T]: T[P];
}
映射类型可以做一些有意思的事情,比如
readonly Person
--> PersonReadonly
--> Person
Partial
Require
DeapReadonly
A
、B
两个对象聚合成一个新对象C
,C
有A
和B
的全部属性,但B
的属性变成可选类型鉴于映射内容比较多且偏向与类型编程,在下一章节《TypeScript类型编程》 将详细介绍这些案例。
class
关键字定义一个类型,在TS中class
还表示声明了一种类型public
private
protected
修饰类的实例属性或方法、静态属性或方法及构造参数属性。
public
默认就是public
在任何地方都可以访问private
私有的,不允许在类的外部访问protected
受保护的,与private
类似,但它可以在子类中访问。readonly
?
修饰 类的实例属性或方法,静态属性或方法tsclass Animal {
static say?() {};
readonly name: string;
static age?: number;
public constructor(name: string) {
this.name = name;
}
}
var ani: Animal = new Animal('cat');
ts// 定义类的实例属性
class Cat {
public name: string; // 方式1
public constructor(name: string) {
this.name = name;
}
}
class Cat2 { // 方式2 在构造器函数参数重定义实例属性,必须有修饰符
public constructor(public name: string) {
// this.name = name; // 这一行代码不用写转JS时自动生成
}
}
tsclass Cat {
public constructor(public name: string) {
this.name = name;
}
}
let cat: Cat = new Cat('Tom');
// 获取类的类型
type MyCat = typeof Cat;
const Cat2: MyCat = class {
public name: string;
public constructor(name: string) {
this.name = name;
}
}
const cat2 = new Cat2('Tom')
// 获取类的实例类型
type InstanceCat = InstanceType<typeof Cat>
const obj: InstanceCat = {
name: 'Tom'
}
abstract
用于定义抽象类和其中的抽象方法
new
的tsabstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
class Cat extends Animal {
public sayHi() {
console.log(`Hello, I am ${this.name}.`);
}
}
抽象类与类之间的关系
implements
关键字实现接口。public
特性ts// 多个类之间共有的特性可以抽取为接口
// 通过implements实现接口
interface Alarm {
alert(): void;
}
class Door implements Alarm {
alert() {
console.log('SecurityDoor alert');
}
}
class Car implements Alarm {
alert() {
console.log('Car alert');
}
}
// 一个类可以实现多个接口
interface Come {
in(key: string): void;
}
class Room implements Alarm, Come {
in(key: string) {
console.log(`you can${key === 'admin' ? ' ' :' not '}come in`);
}
alert() {
console.log('Car alert');
}
}
// 接口可以继承接口,也可以是多继承
interface Secretary extends Come, Alarm {
name: string
}
const secretary: Secretary = {
name: 'songzhe',
in() {},
alert() {}
}
// 接口还可及继承类
interface Baoma extends Car {
footer: number;
}
let bm: Baoma = {
footer: 4,
alert() {},
}
泛型的解释有好几个版本
extends
关键字描述泛型的父类, 这样它就能使用父类的方法和属性了=
ts// 泛型约束条件
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
// 多个泛型参数之间可以相互约束
function copyFields<T extends U, U>(target: T, source: U): T {
for (let id in source) {
target[id] = (<T>source)[id];
}
return target;
}
let x = { a: 1, b: 2, c: 3, d: 4 };
copyFields(x, { b: 10, d: 20 });
// 上例中,我们使用了两个类型参数,其中要求 T 继承 U,
// 这样就保证了 U 上不会出现 T 中不存在的字段。
本文作者:郭敬文
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!