TypeScript 复习进阶三部曲 (1) – 把 TypeScript 当强类型语言使用

前言

原本是想照着 TypeScript 官网 handbook 写个教程的. 但提不起那个劲...

所以呢, 还是用我自己的方式写个复习和进阶笔记就好了呗.

 

学习 TypeScript 的三个阶段

我觉得学 TypeScript 有三个阶段

第一个阶段是把 TypeScript 当 C# / Java 这些静态类型语言来写.

你会用到 Class, Interface, Generic, Enum 这些东西. 但其实这些只是 TypeScript 很小的部分而已.

第二阶段是逻辑类型

C# 是没有办法表达出类型间的逻辑关系的. 你不能表达 "这个变量的类型就是那个函数的第一个参数类型".

但 TypeScript 可以这样表达. 而为了实现这个表达手法, TypeScript 多了很多概念. 你甚至可以把 TypeScript 当成一门编程语言. TS 是一门用来表达 JS 类型的编程语言.

语言意味着它有自己的语法, 编程意味着它能实现基本的编程概念, 比如 variable, function, assign, call, if else, loop 等等.

如果 TypeScript 把格局做大点, 以后任何弱类型语言想强类型化, 都可以使用它.

第三个阶段就是类型体操

如果你只是写业务代码, 你不会上升到第三阶段. 因为没有需求. 但如果你开发 library 就会.

许多语言 (C#) 的 library 都挺可怜的. 一堆的方法重载. 一堆的强转类型. 这是因为语言表达力不够. 

如果有关注 C# 就会发现, 自从 Blozar 想吃前端蛋糕后, C# 加入了许多特性. 就是因为 C# 没办法直接替代 JS, 

在第二阶段我们会用到许多 TypeScript Utility 来表达类型间的逻辑关系, 这些 build-in 的 Utility 是 TypeScript 封装给我们的.

它底层其实是 TypeScript 编程 e.g. variable, function, assign, call, if else, loop

到了第三阶段, build-in 的 Utility 就不够用了. 我们需要自己编写 Utility (类型体操) 和使用 Utility Library e.g. type-festts-toolbelt.

这阶段你就需要对 TS 编程的部分非常了解了.

 

表达与理解

语言嘛, 就是表达和理解, 2 个点.

我们主要都是在学表达的部分, 因为理解是解析器搞的事情, 但是呢, 作为表达者, 我们多少也需要去了解 "听者" 是否明白我们说的哦.

表达

let value: string;

作为表达方, 上面这一句想说的是, 1 个 variable 它的类型是 string.

理解

而作为理解方. 当 variable 的 value 不是 string 时, 必须要警示.

同时, 在编程时, 需要提供 intellisense (help tips)

整个过程就是, 我们表达类型 > IDE 理解类型 > 并做出后续的补助. 这个循环就起来了.

语言的发展与局限

在写 TypeScript 的时候, 我们会有以下几个感受, 这些都是因为 TypeScript 还不够完善.

1. 表达不出来. (感受: 逻辑编不出...只能 hardcode)

2. 理解不对. (感受: er... 难道我说的不对?)

3. 不够聪明. (感受: 笨, 不是告诉你了吗, 怎么还不会?)

遇到的时候不要慌, 上 Github Issue 看看高 thumb 的 feature request. 可能会有 roadmap 或 workaround.

 

类型与 Annotations (声明类型的方式)

我们一边学类型, 一边学如何声明类型吧.

Type Annotation of Variables

从最简单的开始, 给变量声明类型

const value1: string = '';
相等于 C# 的
string value1 = "";

如果 value 不是 string 那么 IDE 就会提示错误.

JavaScript 类型

之前复习 JS 时, 我写了一篇 JavaScript – 数据类型.

JS 有 8 个类型, string, number, boolean, object, null, undefined, bigint, symbol

通过 typeof 可以查出来, 除了 null 因为历史原因被错误的当成 object, 其它都很好理解. bigint 是 es2020 才加入的.

const value1: string = '';
const value2: number = 0;
const value3: boolean = false;
const value4: object = Object.create(null);
const value5: null = null;
const value6: undefined = undefined;
const value7: bigint = 0n;
const value8: symbol = Symbol();

JS 的类型 TypeScript 都支持.

null is null not object

TS 纠正了 JS null is object 的混乱, null 就是 null, value 只能是 null

object vs Object

参考: Stack Overflow – Difference between 'object' ,{} and Object in TypeScript

object 类型其实不是我们直观理解的对象哦, 它其实表示 "除了其它 7 个类型以外的类型"

但为什么 object 不是 Object 呢? 首先你要了解 JS 的对象, 看这篇 JavaScript – 理解 Object, Class, This, Prototype, Function, Mixins

const obj1: Object = {};
const obj2: Object = new Object();
const obj3: Object = Object.create(Object.prototype);
const obj4: object = Object.create(null); // 它是 object not Object

obj 1,2,3 是等价的. 创建出来的对象 __proto__ 都指向 Object.prototype. 所以这个对象可以调用所有 Object.prototype 的方法 (e.g. hasOwnProperty)

但是 obj4 不同, 它的 __proto__ 是 null, 所以无法调用 Object.prototype 的方法.

而 TS 在这里就做了区分. Object 表示对象有原型链对接 Object.prototype. 它可以调用 Object.prototype 的所有方法拥, 而 object 却没有.

这就是它们的区别了.

Type Annotation of Function Parameters and Return

声明函数参数和返回值的类型

function doSomething(param1: string, parma2: number): string {
  return 'value';
}

Type Annotation of Class Property and Method

class Person {
  constructor(age: number) {
    this.age = age;
  }
  name: string = '';
  age: number;

  method(param1: string): string {
    return '';
  }
}

More than JS 类型

除了 JS 的类型外, TS 扩展了很多很多的类型

void

C# 用 void 来表示函数没有返回值.

JS 的函数没有返回的情况下, 依然会返回 undefined.

早年我们也用 void 0 来表示 undefined. 

所以 TypeScript 就用了 void 来表示函数没有返回值.

const value1: undefined = void 0; // 赋值 void 0 到类型 undefined 是可以的
const value2: void = undefined; // 赋值 undefined 到类型 void 是可以的
function doSomething(): void {}

any

顾名思义, any 表示它是任何类型. 什么都能接受.

const value1: any = '';
const value2: any = 0;
const value3: any = false;

不管 value 是什么都可以放入 any 类型里. 

any 是最被滥用的 TypeScript 类型. 尤其是遇到 JS convert to TS 的项目. 为了 by pass error 经常会乱用 any. 以至于大家把这种代码称作 AnyScript...

一个好的 TS 代码要尽可能少的使用 any 类型.

unknown

由于 any 太乱来了. 于是 TypeScript 引入了一个叫 unknown 的类型. unknown 常用来表示"暂时还不知道"的类型.

一旦经过类型判断, 它就不在是 unknown 了.

function doSomething(value: unknown): string {
  // 一开始还不知道 value 类型, 所以 value 什么方法也没有.
  if (typeof value === 'string') {
    // 经过判断, 已知 value 是 string, 于是可以调用 string 方法
    return value.substring(0);
  } else if (typeof value === 'number') {
    // 经过判断, 已知 value 是 number, 于是可以调用 number 方法
    return value.toString();
  } else {
    return 'value';
  }
}

any vs unknown

const any: any = 'whatever type value';
// 因为是任何类型, 所以啥都可以调用, 不报错
any.age = 5;
any.method(); 

const unknown: unknown = 'whatever type value';
// 在不确定类似的时候, 什么都不能调用, 会报错
unknown.age = 5; // error
unknown.method(); // error

Array

const values1: string[] = [''];
const values2: Array<string> = ['']; // 这个写法和上面是等价的
values1.map(v => v); // 知道是 Array 后, 编辑器会提示 Array.prototype 的方法, e.g. map

变量函数

const method: (param1: string) => string = (value) => {
  return '';
};

函数 with args

const method: (...params: string[]) => string = (v1, v2) => {
  return '';
};

constructor able (class)

const personClass: new (...args: unknown[]) => void = class Person {};

 

类型管理和约束

除了类型, TS 也提供了许多额外的类型管理和约束特性. 这些都是 C# / Java 有的.

Abstract

abstract class Person {
  abstract method(param1: string): string;
  name: string = '';
}

class Ali extends Person {
  method(param1: string): string {
    return '';
  }
}

抽象 class 表示这个类不能直接被 new, 只能被其它 class 继承.

抽象方法表示父类只声明类型, 具体实现代码交由子类完成.

Access Modifiers

 

 

 

 

 

 

, Method Overload

 

 

 

Interface, 泛型

Interface, Access Modifiers, Abstract, 泛型 C# / Java 都有, 这些不完全算是类型, 但是和类型息息相关. 所以 TS 也是具备的.

Interface 接口

class 通过 "implements" 声明接口, 这个是 Java 的语法. 

class 可以声明多接口, 不像 extends 只能单继承.

interface CanDoSomething {
  name: string;
  doSomething(param1: string): string;
}

interface CanFly {
  fly(param1: string): void;
}

class Person implements CanDoSomething, CanFly {
  name: string = '';
  fly(param1: string): void {}
  
  doSomething(param1: string): string {
    return '';
  }
}

interface extends interface

接口是可以继承的, 而且可以 multiple inherit 哦

interface Parent1 {
  name: string;
}

interface Parent2 {
  age: number;
}

interface Child extends Parent1, Parent2 { // multiple extends
  fly(param1: string): void;
}

class Person implements Child {
  name: string = '';
  age: number = 0;
  fly(param1: string): void {}
}

interface for function

 

 

 

 

 

 

 

 

 

 

Union Types (联合类型)

联合类型带有 "或者" 的概念. 比如 value is string or number

const value1: string | number = ''; // string 可以接受
const value2: string | number = 0; // number 也可以接受

function doSomething(value: string | number) {
  value.toString(); // 在没有进一步判断类似时, 编辑器只会显示 string 和 number 共同拥有的方法, 比如 toString, valueOf
  if (typeof value === 'string') {
    value.substring(0); // 进一步判断后就可以使用 string 方法了.
  }
}

intersection Types (交叉类型)

交叉类型带有 "和" 的概念, 比如 value is Person and Animal

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章