跳至主要內容

TypeScript

石怜安大约 10 分钟TypeScriptTypeScript

TS中的类

类(Class)面向对象 程序设计 (OOP,Object-Oriented Programming) 实现信息封装的基础

类是一种用户定义的 引用数据 类型,也称类类型

传统的 面向对象语言 基本都是基于类的,JavaScript 基于 原型 的方式让开发者多了很多理解成本

在 ES6 之后,JavaScript 拥有了 class 关键字,虽然本质依然是 构造函数,但是使用起来已经方便了许多

但是 JavaScript 的 class 依然有一些特性还没有加入,比如 修饰符抽象类

TypeScript 的 class 支持 面向对象 的 所有特性,比如 接口

用法

定义类的关键字为 class,后面紧跟类名,类可以包含以下几个模块(类的数据成员):

  • 字段:类里面声明的变量
  • 构造函数:类实例化时调用,可以为类的对象分配内存
  • 方法:对象要执行的操作
class People {
  // 字段
  name: string;

  // 构造函数
  constructor(name: string) {
    this.name = name
  }

  // 方法
  getInfo(): void {
    console.log("姓名为:  " + this.name)
  }
}

继承

类的 继承 使用 extends 的关键字

class People {
  talk(someThing: string) {
    console.log(someThing)
  }
}

class Man extends People {
  run() {
    console.log('The man ran ten meters');
  }
}

const aMan = new Man();

aMan.run(); // The man ran ten meters
aMan.talk("It's exhausting"); // It's exhausting

Man 是一个 派生类,它派生自 Man 基类,派生类 通常被称作 子类基类 通常被称作 超类

Man 类继承了 People 类,因此实例 aMan 也能够使用 People 类 talk 方法

同样,类继承后,子类可以对父类的方法重新定义,这个过程称之为方法的重写

通过 super关键字 是对父类的直接引用,该关键字可以引用父类的属性和方法,如下:

class People {
  talk(someThing: string) {
    console.log(someThing)
  }
}

class Man extends People {
  run() {
    console.log('The man ran ten meters');
  }
  talk() {
    super.talk("People don't want to speak anymore")
    console.log("Man don't want to speak anymore")
  }
}

const aMan = new Man();

aMan.run(); // The man ran ten meters
aMan.talk(); // People don't want to speak anymore    Man don't want to speak anymore

修饰符

公共修饰符 public

可以被访问的类程序里定义的成员

class People {
  public name: string = 'zhangsan';
}

class Man extends People {
  constructor() {
    super();
    console.log(this.name) // zhangsan
  }
}

const aMan = new Man();
console.log(aMan.name) // zhangsan

私有修饰符 private

只能够在该类的 内部 进行访问

class People {
  public name: string = 'zhangsan';
  private age: number = 18;
}

class Man extends People {
  constructor() {
    super();
    console.log(this.name) // zhangsan
    console.log(this.age) // Property 'age' is private and only accessible within class 'People'
  }
}

const aMan = new Man();
console.log(aMan.name) // zhangsan
console.log(aMan.age) // Property 'age' is private and only accessible within class 'People'

受保护修饰符 protect

除了在该类的 内部 可以访问,还可以在 子类 中仍然可以访问

class People {
  public name: string = 'zhangsan';
  private age: number = 18;
  protected sex: 'male' | 'female' = 'male'
}

class Man extends People {
  constructor() {
    super();
    console.log(this.name) // zhangsan
    console.log(this.age) // Property 'age' is private and only accessible within class 'People'
    console.log(this.sex) // male
  }
}

const aMan = new Man();

console.log(aMan.name) // zhangsan
console.log(aMan.age) // Property 'age' is private and only accessible within class 'People'
console.log(aMan.sex) // Property 'sex' is protected and only accessible within class 'People' and its subclasses.

只读修饰符 readonly

通过 readonly 关键字进行声明,只读属性必须在 声明时 或 构造函数里 被 初始化

class People {
  readonly name: string;
  constructor(name: string) {
    this.name = name
  }
}

const people = new People('zhangsan')
people.name = 'lisi' //  Cannot assign to 'name' because it is a read-only property.

静态属性 static

这些属性存在于 类本身 上面而 不是 类实例 上,通过static进行定义,访问这些属性需要通过 类型.静态属性 的这种形式访问

class People {
  static idCard: string = '111xxxxxx11'
  readonly name: string;
  constructor(name: string) {
    this.name = name
    console.log(this.idCard) // Property 'idCard' does not exist on type 'People'. Did you mean to access the static member 'People.idCard' instead?
  }
}

console.log(People.idCard) // 111xxxxxx11

抽象类 abstract

抽象类 做为 其它派生类 的 基类 使用,它们一般不会直接被实例化,不同于接口,抽象类可以包含成员的实现细节

abstract 关键字是用于定义 抽象类 和在抽象类内部定义 抽象方法

abstract class People {
  abstract talk(): void
  run() {
    console.log('The people ran ten meters');
  }
}

class Man extends People {
  talk() {
    console.log("Man don't want to speak anymore");
  }
}

const aMan = new Man()
aMan.run() // The people ran ten meters
aMan.talk() // Man don't want to speak anymore

应用场景

除了日常借助 类 的特性完成日常业务代码,还可以将类(class)也可以作为接口,尤其在 React 工程中是很常用的,如下:

export default class Carousel extends React.Component<Props, State> {}

由于组件需要传入 props 的类型 Props ,同时有需要设置默认 propsdefaultProps,这时候更加适合使用 class 作为接口 先声明一个类,这个类包含组件 props 所需的 类型初始值

// props的类型
export default class Props {
  public children: Array<React.ReactElement<any>> | React.ReactElement<any> | never[] = []
  public speed: number = 500
  public height: number = 160
  public animation: string = 'easeInOutQuad'
  public isAuto: boolean = true
  public autoPlayInterval: number = 4500
  public afterChange: () => {}
  public beforeChange: () => {}
  public selesctedColor: string
  public showDots: boolean = true
}

当需要传入 props 类型的时候直接将 Props 作为接口传入,此时 Props 的作用就是接口,而当需要我们设置defaultProps初始值的时候,我们只需要:

export default class Props {  }
const defaultProps = new Props()

Props 的实例就是 defaultProps初始值,这就是 class 作为 接口 的实际应用,

用一个 class 起到了 接口设置初始值 两个作用,方便统一管理,减少了代码量

typescript 的数据类型

typescript 和 javascript 几乎一样,拥有相同的数据类型,另外在 javascript 基础上提供了 更加实用的类型 供开发使用

在开发阶段,可以为 明确的变量定义为 某种类型,这样 typescript 就能在编译阶段进行 类型检查,当类型不合符预期结果的时候则会出现错误提示

  • boolean(布尔类型)
  • number(数字类型)
  • string(字符串类型)
  • array(数组类型)
  • tuple(元组类型)
  • enum(枚举类型)
  • any(任意类型)
  • null 和 undefined 类型
  • void 类型
  • never 类型
  • object 对象类型

布尔类型 boolean

let flag:boolean = true;
// flag = 123; // 错误
flag = false;  //正确

数字类型 number

typescript 的 数值类型 都是 浮点数,可支持二进制八进制十进制十六进制

let num:number = 123;
// num = '456'; // Type 'string' is not assignable to type 'number'.
num = 456;  //正确
// 进制表示:
let decLiteral: number = 6; // 十进制 6
let hexLiteral: number = 0xf00d; // 十六进制 61453
let binaryLiteral: number = 0b1010; // 二进制 10
let octalLiteral: number = 0o744; // 八进制 484

字符串类型 string

可以使用 双引号单引号 表示字符串

let str: string = 'this is ts';
str = 'test';
let name: string = `Gene`; // 注意编辑器存在部分bug,name会报错,跟window的name冲突
let age: number = 37;
let sentence: string = `Hello, my name is ${ name }`

数组类型 array

通过 [] 进行包裹,有两种写法

// 方式一:元素类型后面接上 []
let arr1: string[] = ['12', '23'];
arr1 = ['45', '56'];

// 方式二:使用数组泛型,Array<元素类型>  
let arr2: Array<number> = [1, 2];
arr2 = [45, 56];

元祖类型 tuple

表示一个 已知元素数量类型 的数组,各元素的 类型可以不相同

赋值的 类型位置个数需要和定义(声明)的一致

let tupleArr: [number, string, boolean];
tupleArr = [12, '34', true];
typleArr = [12, '34'] // Cannot find name 'typleArr'

枚举类型 enum

enum 类型是对 JavaScript 标准数据类型的一个补充

enum Color {Red, Green, Blue}
let c: Color = Color.Green;

任何类型 any

可以指定任何类型的值,在 编程阶段 还不清楚类型的变量指定一个类型,不希望 类型检查器 对这些值进行检查,而是直接让它们通过编译阶段的检查

使用 any 类型允许被赋值为 任意类型,甚至可以调用其 属性方法

let num: any = 123;
num = 'str';
num = true;
// 定义存储各种类型数据的数组时,示例代码如下:
let arrayList: any[] = [1, false, 'fine'];
arrayList[1] = 100;

null 和 和 undefined

在 JavaScript 中 null表示 "什么都没有",是一个只有一个值的特殊类型,表示一个空对象引用,而 undefined 表示一个 没有设置值变量

默认情况下 nullundefined 是所有类型的 子类型就是说你可以把 null和 undefined 赋值给 number 类型的变量

let num: number | undefined; // 数值类型 或者 undefined
console.log(num); // undefined
num = 123;
console.log(num); // 123
num = null;
console.log(num); // null
num = undefined;
console.log(num); // undefined

但是 ts 配置了 --strictNullChecks 标记,null 和 undefined 只能赋值给 void它们各自

无返回值类型 void

用于标识方法返回值的类型,表示该方法没有返回值

function hello(): void {
  alert("Hello Ts");
}

不会出现类型 never

never是其他类型(包括null和 undefined)的子类型,可以赋值给任何类型,代表 从不会出现的值

但是没有类型是 never 的子类型,这意味着声明 never 的变量只能被 never 类型所赋值

never 类型一般用来指定 抛出异常无限循环

let a: never;
a = 123; // Type 'number' is not assignable to type 'never'.

a = (() => { // 正确的写法
  throw new Error('错误');
})()

// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
  throw new Error(message);
}

对象类型 object

let obj: object;
obj = { name: 'ShiLianAn', age: 27 };

总而言之

和 javascript 基本一致,也分成:

  • 基本类型
  • 引用类型

在基础类型上,typescript 增添了 voidanyemum 等原始类型

TS中的枚举类型

枚举 是一个被 命名 的 整型常数 的集合,用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为 枚举类型

通俗来说,枚举 就是 一个对象的所有可能取值的集合

在日常生活中也很常见,例如表示星期的 SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY 就可以看成是一个枚举

枚举的 说明与结构和 联合 相似,其形式为:

enum 枚举名{
    标识符①[=整型常数],
    标识符②[=整型常数],
    ...
    标识符N[=整型常数],
}枚举变量;

用法

通过 enum 关键字 进行定义

enum Direction {  }
let d: Direction;

分类

数字枚举

当我们声明一个枚举类型时,虽然没有给它们赋值,但是它们的值其实是 默认的数字类型,而且 默认从0开始 依次累加

enum Direction {
  Up,   // 值默认为 0
  Down, // 值默认为 1
  Left, // 值默认为 2
  Right // 值默认为 3
}
console.log(Direction.Up === 0); // true
console.log(Direction.Down === 1); // true
console.log(Direction.Left === 2); // true
console.log(Direction.Right === 3); // true

如果将 第一个值 进行赋值后,后面的值也会根据前一个值进行 累加1

enum Direction {
  Up = 10,
  Down,
  Left,
  Right
}

console.log(Direction.Up, Direction.Down, Direction.Left, Direction.Right); // 10 11 12 13
字符串枚举

枚举类型的值其实也可以是 字符串类型

enum Direction {
  Up = 'Up',
  Down = 'Down',
  Left = 'Left',
  Right = 'Right'
}

console.log(Direction['Right'], Direction.Up); // Right Up

如果设定了 一个变量为字符串 之后,后续的字段也 必须赋值字符串,否则报错

enum Direction {
  Up = 'UP',
  Down, // Enum member must have initializer.
  Left, // Enum member must have initializer.
  Right // Enum member must have initializer.
}
异构枚举

即 将 数字枚举字符串枚举 结合起来混合起来使用

enum EnumType {
  No = 0,
  Yes = "YES",
}

通常情况下很少会使用异构枚举

现在一个枚举的案例如下:

// 编译前
enum Direction {
  Up,
  Down,
  Left,
  Right
}

// 编译后
var Direction;
(function (Direction) {
  Direction[Direction["Up"] = 0] = "Up";
  Direction[Direction["Down"] = 1] = "Down";
  Direction[Direction["Left"] = 2] = "Left";
  Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));

上述代码可以看到, Direction[Direction["Up"] = 0] = "Up"可以分成

Direction["Up"] = 0
Direction[0] = "Up"

所以定义枚举类型后,可以通过 正反映射 拿到对应的值,如下:

enum Direction {
  Up,
  Down,
  Left,
  Right
}

console.log(Direction.Up === 0); // true
console.log(Direction[0]); // Up

并且 多处定义 的 枚举 是可以进行 合并 操作

// 编译前
enum Direction {
    Up = 'Up',
    Down = 'Down',
    Left = 'Left',
    Right = 'Right'
}

enum Direction {
    Center = 1
}

// 编译后
var Direction;
(function (Direction) {
  Direction["Up"] = "Up";
  Direction["Down"] = "Down";
  Direction["Left"] = "Left";
  Direction["Right"] = "Right";
})(Direction || (Direction = {}));
(function (Direction) {
  Direction[Direction["Center"] = 1] = "Center";
})(Direction || (Direction = {}));

应用场景

就拿回生活的例子,后端返回的字段使用 0 - 6 标记对应的日期,这时候就可以使用枚举可提高代码可读性

enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}

console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true