Typescript简易入门指南

227 阅读8分钟

看到题目,大部分人可能感觉这个题有点老!都已经2021年了,还在说Typescript的入门。

说的对,但是能力一般,水平有限,也就仅限于能入个门了。

首先介绍一下typescript,typescript是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。当然,这是官方的说法,简单的说法就是这是一个js的库,和当年的Flow一样,是用来解决大型项目代码复杂性的一个工具。

这里解释一下,毕竟这篇文章主要是面对初学者的。众所周知,js是弱类型的脚本语言,所以开发的过程中函数的传参的返回的类型是可以随心所欲的。当一个项目比较大得时候,总是会出现多人协同开发的情况。这是员工A声明的函数被员工B复用,可能会出现不可预知的bug。例如入参类型不同导致的报错,或者因为员工B增加了一个判断导致return的类型不同而报错。当然,这只是一个小的方面,更复杂的情况也不是我能简单的用三言两语所总结的。

那么为了解决这个问题,早年间Facebook公司出品了一款叫Flow的静态类型检查工具,vue2.0版本就是使用的这个作为类型检测工具。虽然Flow现在的处境比较尴尬,但是当年这也是一个解决思路,而且比起typescript的学习路径更加平滑,引入也更加容易,毕竟Flow只是一个工具,而typescript算是一门新语言。但是我说出了大天也没有用,在2021年的今天,typescript已经赢麻了。所以我们就简单的说一说typescript吧,不是谦虚,真的是很简单的说说。

从官方文档里面可以看出,官方认为只需要5分钟就可以入门typescript,至于这个货是否简单我也不确定,官方给的demo永远是简单的,但是用起来就给我一种当年上学时候数学考试一样,学的时候好好的1+1=2,考试时候就变成了“请证明任何大于4的偶数都可以表示成2个奇素数的和”。说远了,我现在一点点介绍typescript的使用方法,如果嫌我说的不好可以直接去看typescript的官方文档,我也感觉官方文档说的挺好的。

说起typescript首先就得说这个数据类型,毕竟JavaScript和typescript就插在了type上了。

typescript拥有js数据类型:布尔型(boolean),数值(number),字符串(string),数组([]),null, undefined, 对象(object),括号里面就是typescript里面的名称。同时建议在typescript中声明变量时使用let去生命。

let str:string = 'test string';
let num: number = 1;
let bool:boolean = false;
let numArr:number[] = [2,2,3];
let strArr:string[] = ['a', 'b'];
let allArr:(number|string) = ['a', 2];
let n:null = null;
let u:undefined = undefined;
let obj:object = {};

里面需要特殊说明的是数组,数组的表达方式还有一种:

let arr:Array<number> = [1,2,3];

后一种数组表达方式前面的Array是用来表示是数组类型,后面尖括号里面表示数组的数据类型,尖括号的这种表达方式被叫做泛型,后面会单独说(大概)。

当然,typescript不只有这么几种数据类型,还有一些js没有的类型,例如元祖,枚举,Any(这个是潘多拉的魔盒),void,Never。

所谓元祖,就是一个规定长度,规定每个位置的元素的数据类型的数组。

let tuple:[string, number] = ['a', 1];

当tuple这个元祖被声明了之后,它的长度和元素类型就被固定了,超过这个长度的被称为越界。从2.7版本开始就不支持越界了,在2.7版本之前越界的元素只要符合元素内声明过的类型就可以使用。当前官方文档的手册指南暂未修改,但是文档版本中2.7版本文档里面有说明。

Any是最可怕的一种类型,当你不知道这个变量是什么类型的时候就可以使用Any,但是当Any越用越会发现,使用Any一时爽,一直使用一直爽,然后typescript就变成anyscript了,看着满满的Any你就会怀疑当初自己是为什么要使用typescript了。简而言之,变量声明的时候使用Any,这个类型就可以随心所欲。

void是空,一般用在函数上

function fn():void {
    console.log('void function');
}

毕竟声明一个void的变量也没什么用,因为void这个类型能赋值的之后undefined和null,而这两个有自己的类型。

Never是永远走不到的一种类型。

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

// 推断的返回值类型为never
function fail() {
    return error("Something failed");
}

// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
    while (true) {
    }
}

这是官方文档给的demo,看的有点混乱,我去知乎上面找到了尤雨溪大神的回答,答题意思是这个类型是用来报错的,可以理解为try catch去使用。

作者:尤雨溪
链接:www.zhihu.com/question/35…
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

举个具体点的例子,当你有一个 union type:

interface Foo {
  type: 'foo'
}

interface Bar {
  type: 'bar'
}

type All = Foo | Bar

在 switch 当中判断 type,TS 是可以收窄类型的 (discriminated union):

function handleValue(val: All) {
  switch (val.type) {
    case 'foo':
      // 这里 val 被收窄为 Foo
      break
    case 'bar':
      // val 在这里是 Bar
      break
    default:
      // val 在这里是 never
      const exhaustiveCheck: never = val
      break
  }
}

注意在 default 里面我们把被收窄为 never 的 val 赋值给一个显式声明为 never 的变量。如果一切逻辑正确,那么这里应该能够编译通过。但是假如后来有一天你的同事改了 All 的类型:

type All = Foo | Bar | Baz

然而他忘记了在 handleValue 里面加上针对 Baz 的处理逻辑,这个时候在 default branch 里面 val 会被收窄为 Baz,导致无法赋值给 never,产生一个编译错误。所以通过这个办法,你可以确保 handleValue 总是穷尽 (exhaust) 了所有 All 的可能类型。

上面的是尤雨溪大神的原文,我直接搬过来了,毕竟我的理解有误的概率还是比较高的。

最后,也是最复杂的类型,枚举。说数组时提及到的泛型我会在后面的文章(如果有)去细说。

枚举(enum)

我就不介绍的太复杂了,简而言之,枚举就是一个对象,基本上我是这么理解的。当然,绝对不可能是一样的,但是还是有一定的相似度的,初步理解的时候可以带入成有其他功能的对象。

首先我们用官网的数字枚举为例

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

如上,我们定义了一个数字枚举, Up使用初始化为 1。 其余的成员会从 1开始自动增长。 换句话说, Direction.Up的值为 1Down2Left3Right4

我们还可以完全不使用初始化器:

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

现在, Up的值为 0Down的值为 1等等。 当我们不在乎成员的值的时候,这种自增长的行为是很有用处的,但是要注意每个枚举成员的值都是不同的。

这是数字枚举的基本用法,凭借着自增长的行为,可以减少我们声明变量的操作。当然,也会有人好奇,这个东西有什么用,那我们就要回到typescript本身的作用上来。typescript是用来做大型项目管理的,当项目过大的时候因为多人同时开发,会有因部分代码每个人理解不同而产生的bug出现,而typescript就可以减少这部分bug的出现。之前说的只是数据类型的不同导致的bug,但是存在一种情况,就是前端后端约定了固定的字段,但是后面接收的人并不知道,导致字段传错了,这个时候枚举就用上了。

假设一个接口,前端后端约定传参限制在1,2,3这三个数字里面,那么在typescript声明变量的时候就可以用枚举。

enum numToBack {
    one = 1,
    two,
    three
}
function fn(num:numToBack) {
    /*TO DO ....*/
}

这样,传入的参数就被限制在1,2,3里面了,这就是我暂时能想到的枚举的用途。

枚举还有字符串枚举,和数字枚举差不多,只不过字符串枚举需要每个变量都有一个值,毕竟字符串么米办法自增。

还有异构枚举,就是指枚举里面的变量都是不同类型的。同时枚举的值可以是需要计算的值

enum FileAccess {
    // constant members
    None,
    Read    = 1 << 1,
    Write   = 1 << 2,
    ReadWrite  = Read | Write,
    // computed member
    G = "123".length
}

而且枚举的类型不一定非要是基础类型

enum ShapeKind {
    Circle,
    Square,
}

interface Circle {
    kind: ShapeKind.Circle;
    radius: number;
}

interface Square {
    kind: ShapeKind.Square;
    sideLength: number;
}

let c: Circle = {
    kind: ShapeKind.Square,
    //    ~~~~~~~~~~~~~~~~ Error!
    radius: 100,
}

interface是typescript的接口类型,我后面的文章(如果有)会细讲。

而且还有一个反向映射的功能,这个官方文档说的就很清楚了,我直接引用官方文档了。

除了创建一个以属性名做为对象成员的对象之外,数字枚举成员还具有了

反向映射

,从枚举值到枚举名字。 例如,在下面的例子中:

enum Enum {
    A
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"

TypeScript可能会将这段代码编译为下面的JavaScript:

var Enum;
(function (Enum) {
    Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {}));
var a = Enum.A;
var nameOfA = Enum[a]; // "A"

大体就是这些,能力一般,水平有限,希望评论区里面能将我文章里面的错误指出来,既避免了误人子弟,也帮助我学习进步。