TypeScript基本知识小结(3) | 青训营笔记

101 阅读4分钟

这是我参与「第五届青训营」笔记创作活动的第11天。今天继续记录老师讲解的TypeScript基本知识。

一、本堂课重点内容:

TypeScript基本知识。

二、详细知识点介绍:

枚举 enum

在 TypeScript 中,枚举是双向赋值。

enum Direction {
  Up,
  Down,
  Left,
  Right
}

console.log(Direction.Up, Direction.Down, Direction.Left, Direction.Right); // 0 1 2 3
console.log(Direction[0]); // Up

利用 tsx 命令编译后,上面的 TypeScript 对应的 JavaScript 代码如下:

var Direction1;
(function (Direction1) {
  // 赋值语句返回值是被赋值的值,所以下面这句等价于 Direction1["Up"] = 0; Direction1[0] = "Up"
  // 这就是双向绑定的含义
  Direction1[Direction1["Up"] = 0] ="Up";
  Direction1[Direction1["Down"] = 1] = "Down";
  Direction1[Direction1["Left"] = 2] = "Left";
  Direction1[Direction1["Right"] = 3] ="Right";
})(Direction1 || (Direction1 = {}));

console.log(Direction1.Up);
console.log(Direction1[0]);

从上面的代码中不难看出 TS 中枚举赋值的 “双向赋值” 的含义。也就是说,如果说对枚举赋了初值。那么用[]来索引,[]中的内容也必须是初始赋值。

enum Direction2 {
  Up = 10,
  Down,
  Left,
  Right
}
console.log(Direction2.Up, Direction2.Down, Direction2.Left, Direction2.Right);   // 10, 11, 12, 13
console.log(Direction2[10]);  // Up
console.log(Direction2[0]); // undefined

从上面的代码中可以看出,TS中的枚举的双向绑定的含义。此外,枚举的赋值是只读的,在枚举之外不可以对枚举的属性进行二次赋值。

enum Direction2 {
  Up = 10,
  Down,
  Left,
  Right
}
Direction2.Down = 12 // 只读属性

上面代码中对于 Down 的二次赋值会导致编译报错。

除此之外,在枚举前常常加上 const 关键字进行修饰,此时我们称为 常量枚举。TS的常量枚举在JS中会被自动内联替换,因此可以提高性能。

泛型 generics

个人感觉TypeScript的类和Class的类非常类似,不过TS中泛型应用的更加灵活,函数返回值、数组等地方都可以直接使用泛型。

函数中的泛型

观察下面这段代码。

function echo(arg) {
  return arg;
}

const str: string = "eeee";
const res = echo(str);

尽管 TS 中的类型推断十分强大,而且上面的代码可以说也是比较清晰了,但是res的类型提示依然是any属性: const res: any。此时,我们可以通过在函数定义时添加泛型来解决这个问题。

function echo<T>(arg: T): T {
  return arg;
}

const str: string = "eeee";
const res = echo(str);

此时,res会明确显示出自己是string类型。

不难看出上面的函数引入泛型的方式是在函数名旁边加上<T>关键字,之后就可以在整个函数作用域里面使用泛型 T。甚至可以把泛型 T 作为这个函数的返回值。

此外,泛型可以帮助元组改变内部顺序:

function swap<T, U>(tuple:[T, U]): [U, T] {
  return [tuple[1], tuple[0]]
}
const res2 = swap(['1', 2]);// res2显示是[number, string]类型

约束泛型

可以通过 T[] 的方式来表示是一个泛型数组,从而可以使用数组的属性(比如说length)和方法(比如说push、map)。下面这段代码就展现了 T[] 的作用:

function echoWithArr<T>(arg: T[]): T[] {
  console.log(arg.length);
  return arg;
}
const res1 = echoWithArr(['1', '2', '3']);// res1显示是string[]类型
const res2 = echoWithArr(['1', 2, '3']);// res2显示是(string | number)[]类型

个人感觉这个T[]实际上是一个元组,因为正如上面的 res2 显示单独那样,T[]并不限制所有的成员都是一个类型。

此外,还可以通过泛型继承接口的方式来对泛型进行约束。

interface IWithLength {
  length: number,
}
function echoWithLength<T extends IWithLength>(arg: T): T {
  // 这样即使arg是泛型,由于继承了接口T,一定有length属性
  console.log(arg.length);
  return arg;
}
const string = echoWithLength('str'); // 输出3
const object = echoWithLength({length: 17}); // 输出17。这里在写对象的时候,甚至有length的提示补全
const array = echoWithLength([1, 2, 3, 4]); // 输出4

// echoWithLength(12); // 报错

接口和类中的泛型

和 Java 一样,泛型在类和接口中有广泛的应用,可以说撑起了TypeScript的半边天。下面是利用泛型写成的一个简化版本的队列类Queue。

class Queue<T> {
  private data: T[] = [];
  push(item: T){
    return this.data.push(item);
  }
  pop() {
    return this.data.shift();
  }
}

const queue = new Queue();
queue.push(1);
queue.push('22');
console.log(queue.pop());

除了上面的基本应用外,还可以如同数组那样用泛型来特化泛型类,比如说可以限制上面定义的队列只能是 number类型。此时,类中所有涉及到泛型的属性和方法都会收到影响:

// 限制只能是 number 类型
const numberQueue = new Queue<number>();
// numberQueue.push('22');  // 报错

下面则是泛型在接口中的应用:

let kp1: KeyPair<number, string> = {key: 1, value: '123'};
let kp2: KeyPair<string, number> = {key: '123', value: 123};
// 官方默认泛型接口Array
let arrTwo: Array<number> = [1, 2, 3, 4, 5];
let arr2: Array<number | string> = [1, 2, 3, 4, 'qe'];

// 使用interface 和 泛型 描述函数类型
interface IPlus<T> {
  (a: T, b: T): T
}
function plus(a: number, b: number): number {
  return a + b;
}
function connect(a: string, b: string): string {
  return a + b;
}
const func1: IPlus<number> = plus;
const func2: IPlus<string> = connect;

三、实践练习例子:

例子已经在文章中举出。

四、课后个人总结:

本章的知识点需要大量的实例和参考资料来辅助理解。

五、引用参考:

我主要是基于老师讲解提供的代码仓库进行理解和分析,并记录了自己的心得。