TS学习(二):进阶使用

简介: TS学习(二):进阶使用

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会检测错误
image.png

通过typeof判断n为string使其可以调用length属性,这就是ts的类型保护
image.png

常见的类型保护:

  • in
  • instanceof
  • typeof
  • 字面量类型

3.2 自定义类型保护

image.png

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());
相关文章
|
2月前
|
JavaScript 前端开发 程序员
ts学习(1)
ts学习(1)
128 69
|
6月前
TS 快速入门
TS 快速入门
40 0
|
6月前
ts笔记
ts笔记
|
6月前
|
JavaScript
使用TS的一些基础知识
使用TS的一些基础知识
79 0
|
6月前
|
JavaScript 前端开发 编译器
ts面试题总结
ts面试题总结
126 0
|
JavaScript 前端开发 开发者
ts知识点
ts知识点
57 0
|
JavaScript 安全 Python
TS笔记
TS笔记
63 0
|
JavaScript 前端开发 开发工具
CocosCreator 面试题(五)TS有什么优缺点?为什么要用TS?
CocosCreator 面试题(五)TS有什么优缺点?为什么要用TS?
185 0
|
JavaScript 索引
ts - 类 进阶2
ES7类的使用
|
JavaScript 前端开发 程序员
ts - 类 进阶1
ES6 类的使用