0%

TypeScript教程一之基本类型

之前的时候都是用 JavaScript 开发,但是最近发现随着项目的推进,一些项目越来越多地要求使用 TypeScript 来开发,所以不得不来专门学习一下 TypeScript 了,本篇文章就简单记录下学习 TypeScript 的过程。

介绍

先看看 TypeScript 的官方介绍:

Typed JavaScript at Any Scale.

TypeScript extends JavaScript by adding types. By understanding JavaScript, TypeScript saves you time catching errors and providing fixes before you run code. Any browser, any OS, anywhere JavaScript runs. Entirely Open Source.

我们知道 JavaScript 是弱类型的语言,而实际上说白了就是加上类型标识的 JavaScript 语言,加上类型标识之后,可以帮助我们减少开发的调试成本,尽早发现错误,节省更多时间。同时 TypeScript 是完全开源的,它编译后得到的 JavaScript 支持任何浏览器和操作系统运行。

截止本文撰写的时间,TypeScript 已经更新到 4.1 版本,官方文档地址:https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html。

相关资料

学习 TypeScript 当然要看原汁原味的资料,推荐 TypeScript 官方的 Handbook,链接为:https://www.typescriptlang.org/docs/handbook/intro.html。

本文所总结的内容基于 Handbook 的内容加上个人的一些修改而成。

另外本节的代码推荐大家直接在 TypeScript 提供的 Playgroud 里面运行即可,不用自己再初始化 Node 和 TypeScript 环境,简单方便,链接为:https://www.typescriptlang.org/play,大家直接打开就行了,预览如下:

image-20201228234110415

比如默认情况下就有两行初始化代码:

1
2
const anExampleVariable = "Hello World";
console.log(anExampleVariable);

点击 Run 之后就可以直接运行,控制台输出结果显示在右侧,内容如下:

1
[LOG]: "Hello World"

如果你运行成功了,那我们就可以开始接下来的学习了。

下面正式开始介绍。

TypeScript 主要就是在 JavaScript 基础上扩展了一些类型,所以这里就分各种类型来进行介绍。

基本类型

Boolean

最常见的基本类型就是布尔类型了,其值就是 true 或者 false,类型声明用 boolean 就好了,在 TypeScript 中,声明一个 boolean 类型的变量写法如下:

1
2
let isDone: boolean = false;
console.log(isDone, typeof isDone);

这里注意到,声明类型可以在变量名的后面加上一个冒号,然后跟一个类型声明,和 Python 的 Type Hint 非常像。然后我们用 console 的 log 方法输出了这个变量,并用 typeof 输出了它的类型。

运行结果如下:

1
[LOG]: false,  "boolean"

可以看到运行结果就被输出出来了,同时 typeof 就是这个变量的类型,结果是一个字符串,就是 boolean。

是不是很简单?基本套路就是在变量的后面跟一个冒号再跟一个类型声明就好了。

Number

Number 即数值类型,对于这个类型,TypeScript 和 JavaScript 是一样的,Number 可以代表整数、浮点数、大整数。其中大整数需要单独用 bigint 来表示。另外 Number 还可以代表十六进制、八进制、二进制。

示例如下:

1
2
3
4
5
6
7
8
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let big: bigint = 100n;

console.log(hex, typeof hex);
console.log(big, typeof big);

其中 bigint 需要 ES2020 的支持,需要在 TS Config 里面设置下:

image-20201228235911831

运行结果如下:

1
2
[LOG]: 61453,  "number"
[LOG]: 100, "bigint"

这里输出了 hex 和 big 变量的值和类型,其中 hex 本身是用十六进制声明的,打印输出的时候被转化为了十进制,同时其类型为 number。

另外对于 bigint 类型来说,值的后面需要加一个 n,即 100 和 100n 代表的类型是不一样的,后者的类型是 bigint,前者的类型是 number。

假如把 bigint 类型的变量声明为 number 类型是会报错的,比如这样就是错误的:

1
let big: number = 100n;

报错内容如下:

1
Type 'bigint' is not assignable to type 'number'.

String

String 即字符串类型,需要用 string 来声明类型。字符串可以用单引号或者双引号或者斜引号声明,其中斜引号就是模板字符串。

示例如下:

1
2
3
4
5
6
7
8
let color: string = "blue";
color = "red";
let fullName: string = `Bob Bobbington`;
let age: number = 37;
let sentence: string = `Hello, my name is ${fullName}. I'll be ${
age + 1
} years old next month.`;
console.log(sentence, typeof sentence);

运行结果如下:

1
[LOG]: "Hello, my name is Bob Bobbington. I'll be 38 years old next month.",  "string"

嗯,没什么特殊的,就是字符串类型。

Array

Array 即数组,声明可以有两种方式,一种是 type[] 这样的形式,一种是 Generics 泛型的形式,示例如下:

1
2
3
4
5
6
let list: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3];
let list3: Array<any> = [1, "2", 3];
console.log(list, typeof list);
console.log(list2, typeof list2);
console.log(list3, typeof list3);

其中 list 就是使用了 type[] 这样的声明方式,声明为 number[],那么数组里面的每个元素都必须要是 number 类型,list2 则是使用了泛型类型的声明,和 list 的效果是一样的,另外 list3 使用了 any 泛型类型,所以其值可以不仅仅为 number,因此这里 list3 的第二个元素设置为了字符串 2。

运行结果如下:

1
2
3
[LOG]: [1, 2, 3],  "object"
[LOG]: [1, 2, 3], "object"
[LOG]: [1, "2", 3], "object"

结果的 typeof 输出的结果可能让人意外,不应该是输出其声明的类型吗,怎么是 object?这是因为浏览器真正执行的是刚才的 TypeScript 编译生成的 JavaScript,而 JavaScript 本身的 Array 和 Object 等类型,typeof 都统一返回 object 类型,因此得到的结果就是 object 了。

Tuple

Tuple 即元组,它可以允许我们声明固定数量和顺序的 Array,来看个例子就懂了:

1
2
3
let x: [string, number];
x = [1, 2]; // Error
x = ["Hello", 2]; // Correct

比如这里声明了一个变量 x,x 必须是一个 Array,按照顺序来说,其第一个元素必须要是 string,第二个元素必须要是 number,所以第二行的声明就会报错,第三行的声明才是正确的。

有了类型声明之后,在编译阶段就能发现错误或者编辑器给出方法输入提示。比如:

1
2
console.log(x[0].substring(1)); // Correct
console.log(x[1].substring(1)); // Error, Property 'substring' does not exist on type 'number'.

比如这里第一行我们在敲 substring 的时候,编辑器根据类型判断出 x[0] 是 string 类型,那就可以给出 substring 的提示。而对于第二行代码的 x[1],编译器会直接检查出其中存在错误,这有助于我们在静态类型检查时候及时发现问题,减少 Bug 出现的概率。

Enum

Enum 即枚举类型,这个非常有用,有时候我们想定义的变量其实就只有某几种取值,那完全可以定义为枚举类型。

用法如下:

1
2
3
4
5
6
7
8
9
10
enum Stage {
Debug,
Info,
Warning,
Error,
Critical,
}

let stage: Stage = Stage.Critical;
console.log(stage, typeof stage);

这里我们声明了一个枚举类型,其名称叫做 Stage,值有五个。然后声明的变量直接用 Stage 修饰即可,值则可以直接取 Stage 的某个值,这里取值为 Stage.Critical

最后打印输出该变量,结果如下:

1
[LOG]: 4,  "number"

结果居然是 4,这个怎么情况?

原来是因为枚举类型,它会按照枚举值的声明顺序自动编号,比如 Debug 的值就是 0,Info 就是 1,以此类推。默认情况下是从 0 开始编号的,不过我们也可以手动更改编号的起始值,比如 Debug 从 1 开始编号,可以声明如下:

1
2
3
4
5
6
7
8
9
10
enum Stage {
Debug = 1,
Info,
Warning,
Error,
Critical,
}

let stage: Stage = Stage.Critical;
console.log(stage, typeof stage);

最后可以看到 Critical 的编号就变成 5 了,运行结果如下:

1
[LOG]: 5,  "number"

有点意思。

那既然它是按照顺序来赋值的,那我如果在前面的值设置大一点,会不会出现后面的值和前面的值相等的情况?比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
enum Stage {
Debug = 1,
Info = 3,
Warning = 2,
Error,
Critical,
}

let stage: Stage = Stage.Error;
let stage2: Stage = Stage.Info;
console.log(stage, typeof stage);
console.log(stage2, typeof stage2);
console.log(stage == stage2);

看看这样会发生什么?这里 Error 按照常理来说会从 Warning 开始自增,值应该为 3,那 Info 我也设置为 3,二者会是相等吗?

运行结果如下:

1
2
3
[LOG]: 3,  "number"
[LOG]: 3, "number"
[LOG]: true

果不其然,二者还都是 3,而且它们就是相等的。所以,对于枚举类型,我们一定要注意声明值的时候最好不要引起自增值和设定值之间的冲突,不然会引入不必要的麻烦。

另外对于枚举类型,我们还可以根据值进行查询,比如对于上述声明,我们不知道哪个值等于 2 或者 3,那可以直接将值传给 Stage 进行查询,得到的结果是一个字符串,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
enum Stage {
Debug = 1,
Info = 3,
Warning = 2,
Error,
Critical,
}

let stage: string = Stage[3];
console.log(stage, typeof stage);

let stage2: string = Stage[2];
console.log(stage2, typeof stage2);

运行结果如下:

1
2
[LOG]: "Error",  "string"
[LOG]: "Warning", "string"

比如这里 Stage[2] 就是查找枚举值等于 2 的枚举名称,结果是 Warning。那值相同的咋办呢?比如 3,看结果它返回的是 Error,看来 Error 把 Info 覆盖掉了,查询的结果是最新的一个枚举名称。注意这里返回的结果是字符串类型,不是枚举类型,它仅仅代表枚举的名称而已。

Unknown

Unknown 即未知类型,一般在类型不确定的情况下可以声明为 unknown 类型。比如说一个数据可能是 API 返回的,它可能是数值类型也可能是字符串类型,并不知道,这时候我们就可以将其声明为 unknown 类型。

示例如下:

1
2
3
4
5
let notSure: unknown = 4;
notSure = "maybe a string instead";

// OK, definitely a boolean
notSure = false;

比如这里 notSure 就声明了 unknown 类型,一开始它是数值类型,但当然也可以是字符串或者布尔类型。

那这样不就没啥用了吗?和 JavaScript 有啥不同吗?其实 unknown 还有其他用处,比如说编译器可以在一些判定条件下对 unknown 的值进行精确化处理,比如说一个 if 条件,判定了类型为布尔类型,那么其他和该类型相关的变量都会执行静态类型检查。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
declare const maybe: unknown;
// 'maybe' could be a string, object, boolean, undefined, or other types
const aNumber: number = maybe;
// Type 'unknown' is not assignable to type 'number'.

if (maybe === true) {
// TypeScript knows that maybe is a boolean now
const aBoolean: boolean = maybe;
// So, it cannot be a string
const aString: string = maybe;
// Type 'boolean' is not assignable to type 'string'.
}

if (typeof maybe === "string") {
// TypeScript knows that maybe is a string
const aString: string = maybe;
// So, it cannot be a boolean
const aBoolean: boolean = maybe;
// Type 'string' is not assignable to type 'boolean'.
}

这里使用 declare 声明了一个类型变量,然后通过类型变量里面的判定条件就能配合检查其他变量的类型了。

Any

和 Unknown 的情形类似,我们还可以使用 any 来代表任意的类型,示例如下:

1
2
3
declare function getValue(key: string): any;
// OK, return value of 'getValue' is not checked
const str: string = getValue("myString");

这里声明了一个方法,返回类型就是 any,这样的话返回类型就可以是任意的结果了。

那 any 和 unknown 有什么不同呢?any 的自由度会更高一点,如果声明为 any,那么静态类型检查都会通过,即使某个变量的属性不存在。示例如下:

1
2
3
4
5
6
7
8
9
let looselyTyped: any = 4;
// OK, ifItExists might exist at runtime
looselyTyped.ifItExists();
// OK, toFixed exists (but the compiler doesn't check)
looselyTyped.toFixed();

let strictlyTyped: unknown = 4;
strictlyTyped.toFixed();
// Error, Object is of type 'unknown'.

可以看到,前两行的静态类型检查是能过的,但是 unknown 就过不了。

所以,什么时候用 unknown 什么时候用 any 呢?可以从含义上来进行区分:如果某个变量我确实就不知道它的类型,即我没办法知道它的类型,那就用 unknown;如果某个值但其实我确实有办法知道可能的类型,但确实它的类型自由度比较高,那就可以被声明为 any。

但在不必要的情况下,尽量减少 any 类型的使用。

Void

Void 一般用于声明方法的返回值,如果一个方法不返回任何结果,那就用 Void,示例如下:

1
2
3
function warnUser(): void {
console.log("This is my warning message");
}

Null 和 Undefined

其实 TypeScript 还专门为 null 和 undefined 声明了类型,一般 null 就声明为 null 类型,undefined 就声明为 undefined 类型,不过这两个并不太常用,示例如下:

1
2
3
// Not much else we can assign to these variables!
let u: undefined = undefined;
let n: null = null;

另外对于其他类型来说,其值也可以是 null 或者 undefined,比如 number 类型的变量,其值为 null 也是完全可以的。不过这里有个前提条件就是,TS Config 里面的 strictNullChecks 应该是关闭状态,不然会报错的。

设置如下:

image-20201229014015393

比如:

1
2
let a: number = undefined;
let b: string = null;

这样就是没问题的了。

Never

这个类型也比较特殊,它代表你永远得不到它的结果。比如一个方法始终抛出一个异常,或者处于一个无限循环中,那么就可以声明为 never,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Function returning never must not have a reachable end point
function error(message: string): never {
throw new Error(message);
}

// Inferred return type is never
function fail() {
return error("Something failed");
}

// Function returning never must not have a reachable end point
function infiniteLoop(): never {
while (true) {}
}

Object

Object 是 JavaScript 本身带有的类型,它表示非原始型的类型,即任何不是 number、string、boolean、bigint、symbol、null、undefined 的类型。

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
declare function create(o: object | null): void;

// OK
create({ prop: 0 });
create(null);
create(42);
// Error, Argument of type '42' is not assignable to parameter of type 'object | null'.
create("string");
// Error, Argument of type '"string"' is not assignable to parameter of type 'object | null'.
create(false);
// Error, Argument of type 'false' is not assignable to parameter of type 'object | null'.
create(undefined);
// Error, Argument of type 'undefined' is not assignable to parameter of type 'object | null'.

不过,一般不用。

Type assertions

一般情况下,我们确确实实知道某个变量属于什么类型,或者在某种情况下确实需要这种类型的转化,则可以显式的声明某个类型,示例如下:

1
2
3
4
let someValue: unknown = "this is a string";
let strLength: number = (someValue as string).length;
let someValue2: unknown = "this is a string";
let strLength2: number = (<string>someValue2).length;

这里有两种使用方式,一种是 as,一种是尖括号声明。

注意

另外值得注意到是,以上的一些类型声明,使用大写形式 Number, String, Boolean, Symbol and Object 也是可以的,不过不推荐这么做,推荐还是用小写的形式。

比如这样其实是可以的:

1
2
3
4
5
function reverse(s: String): String {
return s.split("").reverse().join("");
}

reverse("hello world");

但更推荐使用小写,写成如下形式:

1
2
3
4
5
function reverse(s: string): string {
return s.split("").reverse().join("");
}

reverse("hello world");

总结

以上便是一些基本的类型声明方式,暂时先总结这么多,后面还会继续整理更高级的用法,比如 Function、Interface、Class 等。