TS入门篇 | 详解 TypeScript 类类型(下)

简介: 传统的面向对象语言都是基于类的,而JavaScript是基于原型的。在ES6中拥有了class关键字,虽然它的本质依旧是构造函数,但是能够让开发者更舒服的使用class了。 TypeScript 作为 JavaScript 的超集,自然也是支持 class 全部特性的,并且还可以对类的属性、方法等进行静态类型检测。下面就来看看 TypeScript 中的类类型。

2. 只读修饰符


在类中可以使用readonly关键字将属性设置为只读:

class UserInfo {
  readonly name: string;
  constructor(name: string) {
    this.name = name;
  }
}
const user = new UserInfo("TypeScript");
user.name = "haha"; // error Cannot assign to 'name' because it is a read-only property
复制代码


设置为只读的属性,实例只能读取这个属性值,但不能修改。

需要注意,如果只读修饰符和可见性修饰符同时出现,需要将只读修饰符写在可见修饰符后面。


三、类的类型


1. 属性类型


(1)参数属性

在上面的例子中,都是在类的定义的顶部初始化实例属性,在 constructor 里接收参数然后对实例属性进行赋值,可以使用参数属性来简化这个过程。参数属性就是在 constructor 构造函数的参数前面加上访问限定符:


class A {
  constructor(name: string) {}
}
const a = new A("aaa");
console.log(a.name); // error 类型“A”上不存在属性“name”
class B {
  constructor(public name: string) {}
}
const b = new B("bbb");
console.log(b.name); // "bbb"
复制代码


可以看到,在定义类 B 时,构造函数有一个参数 name,这个 name 使用访问修饰符 public 修饰,此时即为 name 声明了参数属性,也就无需再显式地在类中初始化这个属性了。


(2)静态属性

在 TypeScript 中和 ES6 中一样使用static关键字来指定属性或方法是静态的,实例将不会添加这个静态属性,也不会继承这个静态方法。可以使用修饰符和 static 关键字来指定一个属性或方法:


class Parent {
  public static age: number = 18;
  public static getAge() {
    return Parent.age;
  }
  constructor() {
    //
  }
}
const p = new Parent();
console.log(p.age); // error Property 'age' is a static member of type 'Parent'
console.log(Parent.age); // 18
复制代码


如果使用了 private 修饰道理和之前的一样:

class Parent {
  public static getAge() {
    return Parent.age;
  }
  private static age: number = 18;
  constructor() {
    //
  }
}
const p = new Parent();
console.log(p.age); // error Property 'age' is a static member of type 'Parent'
console.log(Parent.age); // error 属性“age”为私有属性,只能在类“Parent”中访问。
复制代码


(3)可选类属性

TypeScript 还支持可选类属性,也是使用?符号来标记:


class Info {
  name: string;
  age?: number;
  constructor(name: string, age?: number, public sex?: string) {
    this.name = name;
    this.age = age;
  }
}
const info1 = new Info("TypeScript");
const info2 = new Info("TypeScript", 18);
const info3 = new Info("TypeScript", 18, "man");
复制代码


2. 类的类型


类的类型和函数类似,即在声明类时,同时声明了一个特殊的类型,这个类型的名字就是类名,表示类实例的类型;在定义类的时候,声明的除构造函数外所有属性、方法的类型就是这个特殊类型的成员。

定义一个类,并创建实例后,这个实例的类型就是创建他的类:


class People {
  constructor(public name: string) {}
}
let people: People = new People("TypeScript");
复制代码


创建实例时指定 p 的类型为 People 并不是必须的,TS 会推断出他的类型。虽然指定了类型,但是当再定义一个和 People 类同样实现的类 Animal,并且创建实例赋值给 p 的时候,是没有问题的:

class Animal {
  constructor(public name: string) {}
}
let people = new Animal("JavaScript");
复制代码


所以,如果想实现对创建实例的类的判断,还是需要用到instanceof关键字。


四、类的使用


1. 抽象类


抽象类一般用来被其他类继承,而不直接用它创建实例。抽象类和类内部定义抽象方法,使用abstract关键字:

abstract class People {
  constructor(public name: string) {}
  abstract printName(): void;
}
class Man extends People {
  constructor(name: string) {
    super(name);
    this.name = name;
  }
  printName() {
    console.log(this.name);
  }
}
const m = new Man(); // error Expected 1 arguments, but got 0.
const man = new Man("TypeScript");
man.printName(); // 'TypeScript'
const p = new People("TypeScript"); // error Cannot create an instance of an abstract class.
复制代码


这里定义了一个抽象类 People,在抽象类里定义 constructor 方法必须传入一个字符串类型参数,并把这个 name 参数值绑定在创建的实例上;使用abstract关键字定义一个抽象方法 printName,这个定义可以指定参数,指定参数类型,指定返回类型。当直接使用抽象类 People 实例化的时候,就会报错,只能创建一个继承抽象类的子类,使用子类来实例化。


再看下面的例子:

abstract class People {
  constructor(public name: string) {}
  abstract printName(): void;
}
class Man extends People {  // error Non-abstract class 'Man' does not implement inherited abstract member 'printName' from class 'People'
  constructor(name: string) {
    super(name);
    this.name = name;
  }
}
const m = new Man("TypeScript");
m.printName(); // error m.printName is not a function
复制代码


可以看到,第5行报错:非抽象类“Man”不会实现继承自“People”类的抽象成员"printName"。在抽象类里定义的抽象方法,在子类中是不会继承的,所以在子类中必须实现该方法的定义。


TypeScript 的abstract关键字不仅可以标记类和类里面的方法,还可以标记类中定义的属性和存取器:

abstract class People {
  abstract name: string;
  abstract get insideName(): string;
  abstract set insideName(value: string);
}
class Pp extends People {
  name: string;
  insideName: string;
}
复制代码


注意: 抽象方法和抽象存取器都不能包含实际的代码块。


2. 存取器


存取器就是 ES6 标准中的存值函数和取值函数,也就是在设置属性值的时候调用的函数,和在访问属性值的时候调用的函数,用法和写法和 ES6 的没有区别,可以通过getter、setter截取对类成员的读写访问:


class UserInfo {
  private name: string;
  constructor() {}
  get userName() {
    return this.name;
  }
  set userName(value) {
    console.log(`setter: ${value}`);
    this.name = value;
  }
}
const user = new UserInfo();
user.name = "TypeScript"; // "setter: TypeScript"
console.log(user.name); // "TypeScript"
复制代码


五、类的接口


1. 类类型接口


使用接口可以强制一个类的定义必须包含某些内容:

interface FoodInterface {
  type: string;
}
class FoodClass implements FoodInterface {
  // error Property 'type' is missing in type 'FoodClass' but required in type 'FoodInterface'
  static type: string;
  constructor() {}
}
复制代码


上面接口 FoodInterface 要求使用该接口的值必须有一个 type 属性,定义的类 FoodClass 要使用接口,需要使用关键字implementsimplements关键字用来指定一个类要继承的接口,如果是接口和接口、类和类直接的继承,使用extends,如果是类继承接口,则用implements。


注意,接口检测的是使用该接口定义的类创建的实例,所以上面例子中虽然定义了静态属性 type,但静态属性不会添加到实例上,所以还是报错,可以这样改:

interface FoodInterface {
  type: string;
}
class FoodClass implements FoodInterface {
  constructor(public type: string) {}
}
复制代码


当然也可以使用抽象类实现:

abstract class FoodAbstractClass {
  abstract type: string;
}
class Food extends FoodAbstractClass {
  constructor(public type: string) {
    super();
  }
}
复制代码


2. 接口继承类


接口可以继承一个类,当接口继承了该类后,会继承类的成员,但是不包括其实现,也就是只继承成员以及成员类型。接口还会继承类的privateprotected修饰的成员,当接口继承的这个类中包含这两个修饰符修饰的成员时,这个接口只可被这个类或他的子类实现:


class A {
  protected name: string;
}
interface I extends A {}
class B implements I {} // error Property 'name' is missing in type 'B' but required in type 'I'
class C implements I {
  // error 属性“name”受保护,但类型“C”并不是从“A”派生的类
  name: string;
}
class D extends A implements I {
  getName() {
    return this.name;
  }
}
复制代码


六、其他


1. 在泛型中使用类类型


先来看个例子:

const create = <T>(c: { new (): T }): T => {
  return new c();
};
class Info {
  age: number;
}
create(Info).age;
create(Info).name; // error 类型“Info”上不存在属性“name”
复制代码


这里创建了一个 create 函数,传入的参数是一个类,返回的是一个类创建的实例,注意:

  • 参数 c 的类型定义中,new()代表调用类的构造函数,他的类型也就是类创建实例后的实例的类型。
  • return new c()这里使用传进来的类 c 创建一个实例并返回,返回的实例类型也就是函数的返回值类型。

所以通过这个定义,TypeScript 就知道,调用 create 函数,传入的和返回的值都应该是同一个类类型。

相关文章
|
14天前
|
JavaScript 编译器
TypeScript中类型守卫:缩小类型范围的艺术
【4月更文挑战第23天】TypeScript中的类型守卫是缩小类型范围的关键技术,它帮助我们在运行时确保值的精确类型,提升代码健壮性和可读性。类型守卫包括`typeof`(检查原始类型)、`instanceof`(检查类实例)和自定义类型守卫。通过这些方法,我们可以更好地处理联合类型、泛型和不同数据源,降低运行时错误,提高代码质量。
|
14天前
|
JavaScript 编译器
TypeScript中泛型在函数和类中的应用
【4月更文挑战第23天】TypeScript的泛型在函数和类中提供了灵活性,允许处理多种数据类型。了解泛型是掌握TypeScript类型系统的关键。
|
7天前
|
JavaScript 安全 前端开发
【TypeScript技术专栏】TypeScript中的类型推断与类型守卫
【4月更文挑战第30天】TypeScript的类型推断与类型守卫是提升代码安全的关键。类型推断自动识别变量类型,减少错误,包括基础、上下文、最佳通用和控制流类型推断。类型守卫则通过`typeof`、`instanceof`及自定义函数在运行时确认变量类型,确保类型安全。两者结合使用,优化开发体验,助力构建健壮应用。
|
7天前
|
JavaScript 前端开发 开发者
【TypeScript技术专栏】TypeScript类型系统与接口详解
【4月更文挑战第30天】TypeScript扩展JavaScript,引入静态类型检查以减少错误。其类型系统包括基本类型、数组等,而接口是定义对象结构的机制。接口描述对象外形,不涉及实现,可用于规定对象属性和方法。通过声明、实现接口,以及利用可选、只读属性,接口继承和合并,TypeScript增强了代码的健壮性和维护性。学习和掌握TypeScript的接口对于大型项目开发至关重要。
|
7天前
|
JavaScript 前端开发
TypeScript基础类型
TypeScript基础类型
|
7天前
|
JavaScript 前端开发
typescript 混合类型
typescript 混合类型
|
13天前
|
JavaScript 前端开发 开发者
类型检查:结合TypeScript和Vue进行开发
【4月更文挑战第24天】TypeScript是JavaScript超集,提供类型注解等特性,提升代码质量和可维护性。Vue.js是一款高效前端框架,两者结合优化开发体验。本文指导如何配置和使用TypeScript与Vue:安装TypeScript和Vue CLI,创建Vue项目时选择TypeScript支持,配置`tsconfig.json`,编写`.tsx`组件,最后运行和构建项目。这种结合有助于错误检查和提升开发效率。
|
14天前
|
JavaScript 编译器 开发者
TypeScript中的类型推断机制:原理与实践
【4月更文挑战第23天】TypeScript的类型推断简化编码,提高代码可读性。编译器基于变量初始值或上下文推断类型,若新值不兼容则报错。文章深入探讨了类型推断原理和实践,包括基本类型、数组、函数参数与返回值、对象类型的推断,并提醒注意类型推断的限制,如非万能、类型兼容性和适度显式指定类型。了解这些能帮助更好地使用TypeScript。
|
14天前
|
JavaScript 前端开发 编译器
TypeScript中的高级类型:联合类型、交叉类型与条件类型深入解析
【4月更文挑战第23天】探索TypeScript的高级类型。这些特性增强类型系统的灵活性,提升代码质量和维护性。
|
14天前
|
JavaScript 安全 编译器
TypeScript中类型断言的使用与风险
【4月更文挑战第23天】TypeScript中的类型断言用于显式指定值的类型,但在不恰当使用时可能引发运行时错误或降低代码可读性。