1. 接口
接口是一系列抽象方法的声明,是一些方法特征的集合。简单来说,接口的作用就是为这些类型命名和为你的代码或第三方代码契约
1.1 接口的声明
interface Person {
name: string,
age?: number, // 可选属性
readonly height: number, // 只读属性
[index: number]: string, // 索引签名
}
1.2 接口的继承
接口可以多继承
interface Student extends Person,Animal {
sno: number
}
1.3 接口的实现
接口定义后,也是可以被类实现,通过implements关键字实现接口
- 一个类可以实现多个接口,并且必须实现接口中对应的属性和方法;
- 如果被一个类实现,那么在之后需要传入接口的地方,都可以将这个类传入;
class Student implements Person {
name: string;
sno: string;
constructor(name: string, sno: string) {
this.name = name;
this.sno = sno;
}
}
function study(p: Person) {
console.log(p);
}
const s = new Student("coder", "001");
study(s);
接口和类型别名的区别
- 别名可以给基本类型,接口后面只能接对象类型
- 接口可以合并(重复的对某个接口来定义属性和方法),而别名不能
- 接口具备继承能力,而别名不行
- type具备映射类型,接口不支持
2. 字面量类型和keyof关键字
2.1 字面量类型
const message: 'hello' = 'hello'
默认情况下这么做是没有太大的意义的,但是我们可以将多个类型联合在一起;
type Alignment = 'right' | 'left' | 'center'
const align: Alignment = 'right'
2.2 keyof 关键字
interface A {
username: string;
age:number
}
let a: keyof A // age | usernmae
keyof
关键字可以获取对象中的所有键类型组成的联合类型。
3. 类型保护与自定义类型保护
3.1 类型保护
类型保护允许你使用更小范围下的对象类型
看下图报错,因为n可能是字符串也可能是数字,所以ts会检测错误
通过typeof判断n为string使其可以调用length属性,这就是ts的类型保护
常见的类型保护:
- in
- instanceof
- typeof
- 字面量类型
3.2 自定义类型保护
4. 定义泛型和泛型常见操作
4.1 认识泛型
泛型:将类型进行参数化
- 需要在这里使用一种特性的变量 - 类型变量(type variable),它作用于类型,而不是值
function foo<T>(arg: T): T {
return arg
}
上面的函数可以使用两种方式来调用它:
- 方式一:通过 <类型> 的方式将类型传递给函数
- 方式二:通过类型推到,自动推到出我们传入变量的类型
// 方式一
console.log(foo<string>('hello'))
console.log(foo<number>(1))
// 方式二
console.log(foo('hello'))
console.log(foo(1))
4.2 基本补充
可以传入多个类型:
function foo<T,K>(a1: T, a2: K) {
}
平时在开发中我们可能会看到一些常用的名称:
- T:Type的缩写,类型
- K、V:key和value的缩写,键值对
- E:Element的缩写,元素
- O:Object的缩写,对象
4.3 泛型接口
interface IPerson<T> {
name: T,
friends: T[]
foo: (num: T) => void
}
4.4 泛型类
class Person<T> {
x: T
y: T
constructor(x: T, y: T) {
console.log(x, y)
}
}
4.5 泛型约束
有时候我们希望传入的类型有某些共性,但是这些共性可能不是在同一种类型中:
- 比如string和array都是有length的,或者某些对象也是会有length属性的;
- 那么只要是拥有length的属性都可以作为我们的参数类型,那么应该如何操作呢?
interface ILength {
length: number;
}
function getLength<T extends ILength>(l: T) {
console.log(l.length);
}
getLength("123");
getLength([1, 2, 3]);
5. 类型兼容性
类型兼容性用于确定一个类型是否能赋值给其他类型
let a: number = 123;
let b: string | number = "hello";
// a = b; // 不能将b赋值给a 因为b有可能是字符串
// b = a; // 可以将a赋值给b
let a: { username: string } = { username: "123" };
let b: { username: string; age: number } = { username: "123", age: 123 };
//a = b // success
// b = a // error
6. 映射类型与内置工具类型
6.1 映射类型
可以将已知类型的每个属性都变为可选的或者可读的
type A = {
username: string,
age: 24
}
type B<T> = {
[P in keyof T]: T[P] // in 相当于循环遍历了泛型T中的类型
}
type C = B<A> // 别名C和别名A具有相同的类型
6.2 内置工具类型
Partial
Partial<T>
的作用就是将某个类型里的属性全部变为可选项 ? 。
// 源码
type Partial<T> = {
[P in keyof T]?: T[P];
};
interface Foo {
name: string
age: number
}
type Bar = Partial<Foo>
// 相当于
type Bar = {
name?: string
age?: number
}
ReadOnly
Readonly<T>
的作用是将某个类型所有属性变为只读属性,也就意味着这些属性不能被重新赋值。
// 源码
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface Foo {
name: string
age: number
}
type Bar = Readonly<Foo>
// 相当于
type Bar = {
readonly name: string
readonly age: number
}
Pick
Pick<T, K extends keyof T>
的作用是将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型。
// 源码
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
interface Foo {
name: string;
age?: number;
gender: string;
}
type Bar = Pick<Foo, 'age' | 'gender'>
// 相当于
type Bar = {
age?: number
gender: string
}
const todo: Bar= {
age?: 3,
gender: 男
};
console.log(todo)
Record
Record<K extends keyof any, T>
的作用是将 K
中所有的属性的值转化为 T
类型。
// 源码
type Record<K extends keyof any, T> = {
[P in K]: T;
};
type E = Record<"username" | "age", string>;
// 相当于
type E = {
username: string;
age: string;
}
Required
Required<T>
的作用就是将某个类型里的属性全部变为必选项。
type Required<T> = {
[P in keyof T]-?: T[P];
};
type A = {
username?: string,
age?: number
}
type B = Required<A>
// 相当于
type B = {
username: string;
age: number;
}
Omit
和 Pick
是相反的操作,Omit<T, K extends keyof any>
的作用是使用 T
类型中除了 K
类型的所有属性,来构造一个新的类型。
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type A = {
username: string,
age: number
}
type B = Omit<A, 'username'>
//相当于
type B = {
age: number;
}
Exclude
Exclude<T, U>
的作用是将 T
中的属于 U
的类型移除掉
type Exclude<T, U> = T extends U ? never : T;
type A = Exclude<string | number | boolean, string>;
// 相当于
type A = number | boolean
Extract
Extract<T, U>
的作用是从 T
中提取出 U
。
type Extract<T, U> = T extends U ? T : never;
type A = Extract<string | number | boolean, string>;
// 相当于
type A = string
NonNullable
NonNullable<T>
的作用是用来过滤类型中的 null
及 undefined
类型。
type NonNullable<T> = T & {};
type A = NonNullable<string | null | undefined>;
// 相当于
type A = string
Parameters
Parameters<T>
的作用是用于获得函数的参数类型组成的元组类型。
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
type Foo = (n: number, m: string) => void;
type a = Parameters<Foo>
// 相当于
type a = [n: number, m: string]
ReturnType
ReturnType<T>
的作用是用于获取函数 T 的返回类型。
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
type Foo = (n: number, m: string) => number;
type a = ReturnType<Foo>
// 相当于
type a = number
7. 类中如何使用类型
7.1 类的定义
- 使用class关键字来定义一个类;
声明一些类的属性:在类的内部声明类的属性以及对应的类型
- 如果类型没有声明,那么它们默认是any的;
- 也可以给属性设置初始化值;
- 在默认的strictPropertyInitialization模式下面属性是必须初始化的,如果没有初始化,那么编译时就会报错;如果想要不初始化,可以使用 name!: string语法
类可以有自己的构造函数constructor,当我们通过new关键字创建一个实例时,构造函数会被调用;
- 构造函数不需要返回任何值,默认返回当前创建出来的实例;
- 类中可以有自己的函数,定义的函数称之为方法;
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
eating() {
console.log(this.name + " is eating");
}
}
const p = new Person("coder", 22);
p.eating();
7.2 类的继承
- 面向对象的其中一大特性就是继承,继承不仅仅可以减少代码量,也是多态的使用前提
- 使用extends关键字来实现继承,子类中使用super来访问父类
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
eating() {
console.log(this.name + " is eating");
}
}
class Student extends Person {
sno: string;
constructor(name: string, age: number, sno: string) {
super(name, age);
this.sno = sno;
}
studying() {
console.log("学号是" + this.sno + "的学生正在学习");
}
}
const s = new Student("yjw", 22, "001");
s.eating();
s.studying();
- Student类可以有自己的属性和方法,并且会继承Person的属性和方法;
- 在构造函数中,通过super来调用父类的构造方法,对父类中的属性进行初始化;
7.3 类的成员修饰符
在TypeScript中,类的属性和方法支持三种修饰符: public、private、protected
- public 修饰的是在任何地方可见、公有的属性或方法,默认编写的属性就是public的;
- private 修饰的是仅在同一类中可见、私有的属性或方法;
- protected 修饰的是仅在类自身及子类中可见、受保护的属性或方法;
- public是默认的修饰符,也是可以直接访问的,这里来演示一下protected和private。
class Person {
private name: string;
protected age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
eating() {
console.log(this.name, this.age);
}
}
class Student extends Person {
sno: string;
constructor(name: string, age: number, sno: string) {
super(name, age);
this.sno = sno;
}
}
const p = new Person("coder", 22);
// console.log(p.name); // × 编译错误 属性“name”为私有属性,只能在类“Person”中访问。
// console.log(p.age); // × 编译错误 属性“age”受保护,只能在类“Person”及其子类中访问。
p.eating(); // √
7.4 只读属性readonly
- 如果有一个属性不希望外界可以任意的修改,只希望确定值后直接使用,那么可以使用readonly:
class Person {
readonly name:string
constructor(name: string) {
this.name = name
}
}
const p = new Person('coder')
p.name = 'coderyjw' // × 无法分配到 "name" ,因为它是只读属性
7.5 getters/setters
在前面一些私有属性我们是不能直接访问的,或者某些属性我们想要监听它的获取(getter)和设置(setter)的过程,
这个时候我们可以使用存取器。
class Person {
private _name: string;
constructor(name: string) {
this._name = name;
}
get name(): string {
return this._name;
}
set name(newName) {
this._name = newName;
}
}
const p = new Person("coder");
p.name = 'coderyjw'
console.log(p.name);
7.6 static 静态成员
用于定义类级别的成员和方法。
class Person {
static time: string = "2021-12-26";
}
console.log(Person.time);
4.7 抽象类
// abstract定义的类叫抽象类
// 抽象类不能被实例化,抽象类
// 抽象类中必须要有抽象方法
abstract class Shape {
// abstract定义的函数叫抽象函数
// 抽象方法,必须存在于抽象类中;
// 抽象方法必须被子类实现,否则该类必须是一个抽象类;
abstract getArea(): number;
}
class Circle extends Shape {
r: number;
constructor(r: number) {
super();
this.r = r;
}
getArea(): number {
return this.r * this.r * Math.PI;
}
}
const c = new Circle(5);
console.log(c.getArea());