安装

  1. npm install typescript -g

开始

安装完ts后让我们新建一个文件来进行TS操作,可以通过手动建,也可以用命令行:

  1. mkdir TS
  2. cd TS
  3. touch hello.ts
  4. // 创建完文件后 在文件中输入:
  5. console.log("Hello world!");
  6. // 并在控制台执行
  7. tsc hello.ts
  8. // 会生成一个.js文件

现在开始,让我们开始走进typeScript吧~

常用类型

基本常见类型构建复杂类型的基础

js中的原始类型:string、number、boolean 它们在typeScript中都有对应的类型,并与操作符typeOf的结果一样;

  1. const firstName: string = 'mike';
  2. console.log(firstName);
  3. const age: number = 11;
  4. console.log(age);
  5. const isTrue: boolean = true;
  6. console.log(isTrue);

数组

数组有两种书写方式number[]、Array

  1. let arr1: number[] = [1, 2, 3];
  2. let arr2: Array<number> = [1, 2, 3];
  3. console.log(arr1, arr2);
  4. // 注意 [number] 和 number[] 是不同的两个概念前者代表的是元组,后者表示的是由数字组成的数组

any

any代表任意类型,即如果把类型设置为any,与没有设置类型相同,以下都不会报错。

  1. let obj: any = { x: 0 };
  2. // None of the following lines of code will throw compiler errors.
  3. // Using `any` disables all further type checking, and it is assumed
  4. // you know the environment better than TypeScript.
  5. obj.foo();
  6. obj();
  7. obj.bar = 100;
  8. obj = "hello";
  9. const n: number = obj;

函数(Function)

函数是 JavaScript 传递数据的主要方法。TypeScript 允许你指定函数的输入值和输出值的类型。

参数类型注解

  1. // Parameter type annotation
  2. function greet(name: string) {
  3. console.log("Hello, " + name.toUpperCase() + "!!");
  4. }
  5. // 当参数有了类型注解的时候,TypeScript 便会检查函数的实参:
  6. // 类型“number”的参数不能赋给类型“string”的参数。ts(2345)
  7. greet(11);

返回值类型注解

在这个函数中可以看到在参数列表后面加上了返回值注解,其实在实际应用中, 我们不必每个函数都添加

  1. function getFavoriteNumber(): number {
  2. return 26;
  3. }

TypeScript 会基于它的 return 语句推断函数的返回类型。像这个例子中,类型注解写和没写都是一样的,但一些代码库会显式指定返回值的类型,可能是因为需要编写文档,或者阻止意外修改,亦或者仅仅是个人喜好。


匿名函数

匿名函数有一点不同于函数声明,当 TypeScript 知道一个匿名函数将被怎样调用的时候,匿名函数的参数会被自动的指定类型。
这是一个例子:

  1. // No type annotations here, but TypeScript can spot the bug
  2. const names = ["Alice", "Bob", "Eve"];
  3. // Contextual typing for function
  4. names.forEach(function (s) {
  5. console.log(s.toUppercase());
  6. // Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
  7. });
  8. // Contextual typing also applies to arrow functions
  9. names.forEach((s) => {
  10. console.log(s.toUppercase());
  11. // Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
  12. });

尽管参数 s 并没有添加类型注解,但 TypeScript 根据 forEach 函数的类型,以及传入的数组的类型,最后推断出了 s 的类型。
这个过程被称为上下文推断(contextual typing),因为正是从函数出现的上下文中推断出了它应该有的类型。

对象类型

除了原始类型,最常见的就是对象类型了

  1. // The parameter's type annotation is an object type
  2. function printCoord(pt: { x: number; y: number }) {
  3. console.log("The coordinate's x value is " + pt.x);
  4. console.log("The coordinate's y value is " + pt.y);
  5. }
  6. printCoord({ x: 3, y: 7 });

可选属性

对象类型可以指定一个或者所有属性为可选类型,只需要在属性后面添加 ?.

  1. function printName(obj: { first: string; last?: string }) {
  2. // ...
  3. }
  4. // Both OK
  5. printName({ first: "Bob" });
  6. printName({ first: "Alice", last: "Alisson" });

注意在 JavaScript 中,如果你获取一个不存在的属性,你会得到一个 undefined 而不是一个运行时错误。因此,当你获取一个可选属性时,你需要在使用它前,先检查一下是否是 undefined。

  1. function printName(obj: { first: string; last?: string }) {
  2. // Error - might crash if 'obj.last' wasn't provided!
  3. console.log(obj.last.toUpperCase());
  4. // Object is possibly 'undefined'.
  5. if (obj.last !== undefined) {
  6. // OK
  7. console.log(obj.last.toUpperCase());
  8. }
  9. // A safe alternative using modern JavaScript syntax:
  10. // 即可选链的方式,也是我们现在推荐的方式
  11. console.log(obj.last?.toUpperCase());
  12. }

联合类型

TypeScript 类型系统允许你使用一系列的操作符,基于已经存在的类型构建新的类型。现在我们知道如何编写一些基础的类型了,是时候把它们组合在一起了。
简单理解就是把一些基础类型组合在一起,让我们来看个例子

  1. // Parameter type annotation
  2. function greet(name: string | number) {
  3. console.log("Hello, " + name + "!!");
  4. }
  5. // 当我们使用联合类型的时候,这就不会报错了
  6. greet(11);

注意如果我们使用了联合类型,就不能使用单一类型上存在的方法了:

  1. function printId(id: number | string) {
  2. console.log(id.toUpperCase());
  3. // Property 'toUpperCase' does not exist on type 'string | number'.
  4. // Property 'toUpperCase' does not exist on type 'number'.
  5. }

解决方案是用代码收窄联合类型,就像你在 JavaScript 没有类型注解那样使用。当 TypeScript 可以根据代码的结构推断出一个更加具体的类型时,类型收窄就会出现。
举个例子,TypeScript 知道,对一个 string 类型的值使用 typeof 会返回字符串 “string”:

  1. function printId(id: number | string) {
  2. if (typeof id === "string") {
  3. // In this branch, id is of type 'string'
  4. console.log(id.toUpperCase());
  5. } else {
  6. // Here, id is of type 'number'
  7. console.log(id);
  8. }
  9. }

类型别名

我们已经学会在类型注解里直接使用对象类型和联合类型,这很方便,但有的时候,一个类型会被使用多次,此时我们更希望通过一个单独的名字来引用它。
这就是类型别名(type alias)。所谓类型别名,顾名思义,一个可以指代任意类型的名字。类型别名的语法是:

  1. type Point = {
  2. x: number;
  3. y: number;
  4. };
  5. // Exactly the same as the earlier example
  6. function printCoord(pt: Point) {
  7. console.log("The coordinate's x value is " + pt.x);
  8. console.log("The coordinate's y value is " + pt.y);
  9. }
  10. printCoord({ x: 100, y: 100 });

注意别名是唯一的别名,你不能使用类型别名创建同一个类型的不同版本。当你使用类型别名的时候,它就跟你编写的类型是一样的。换句话说,代码看起来可能不合法,但对 TypeScript 依然是合法的,因为两个类型都是同一个类型的别名:

  1. type UserInputSanitizedString = string;
  2. function sanitizeInput(str: string): UserInputSanitizedString {
  3. return sanitize(str);
  4. }
  5. // Create a sanitized input
  6. let userInput = sanitizeInput(getInput());
  7. // Can still be re-assigned with a string though
  8. userInput = "new input";

接口

接口声明(interface declaration)是命名对象类型的另一种方式:

  1. interface Point {
  2. x: number;
  3. y: number;
  4. }
  5. function printCoord(pt: Point) {
  6. console.log("The coordinate's x value is " + pt.x);
  7. console.log("The coordinate's y value is " + pt.y);
  8. }
  9. printCoord({ x: 100, y: 100 });

类型别名和接口的区别

类型别名和接口非常相似,大部分时候,你可以任意选择使用。接口的几乎所有特性都可以在 type 中使用,两者最关键的差别在于类型别名本身无法添加新的属性,而接口是可以扩展的。

  1. // Interface
  2. // 通过继承扩展类型
  3. interface Animal {
  4. name: string
  5. }
  6. interface Bear extends Animal {
  7. honey: boolean
  8. }
  9. const bear = getBear()
  10. bear.name
  11. bear.honey
  12. // Type
  13. // 通过交集扩展类型
  14. type Animal = {
  15. name: string
  16. }
  17. type Bear = Animal & {
  18. honey: boolean
  19. }
  20. const bear = getBear();
  21. bear.name;
  22. bear.honey;
  1. // Interface
  2. // 对一个已经存在的接口添加新的字段
  3. interface Window {
  4. title: string
  5. }
  6. interface Window {
  7. ts: TypeScriptAPI
  8. }
  9. const src = 'const a = "Hello World"';
  10. window.ts.transpileModule(src, {});
  11. // Type
  12. // 创建后不能被改变
  13. type Window = {
  14. title: string
  15. }
  16. type Window = {
  17. ts: TypeScriptAPI
  18. }
  19. // Error: Duplicate identifier 'Window'.

类型断言

有的时候,你知道一个值的类型,但 TypeScript 不知道。
举个例子,如果你使用 document.getElementById,TypeScript 仅仅知道它会返回一个 HTMLElement,但是你却知道,你要获取的是一个 HTMLCanvasElement。

  1. const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

一般通过<> 或者 as 来表示
谨记:因为类型断言会在编译的时候被移除,所以运行时并不会有类型断言的检查,即使类型断言是错误的,也不会有异常或者 null 产生。
TypeScript 仅仅允许类型断言转换为一个更加具体或者更不具体的类型。这个规则可以阻止一些不可能的强制类型转换,比如:

  1. const x = "hello" as number;
  2. // Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

有的时候,这条规则会显得非常保守,阻止了你原本有效的类型转换。如果发生了这种事情,你可以使用双重断言,先断言为 any (或者是 unknown),然后再断言为期望的类型:

  1. const a = (expr as any) as T;

字面量类型

除了常见的类型 string 和 number ,我们也可以将类型声明为更具体的数字或者字符串。
比如antd里面table 表头fixed的值,字面量类型本身没有太大用处,如果结合联合类型,就显得有用多了。举个例子,当函数只能传入一些固定的字符串时:

  1. function printText(s: string, alignment: "left" | "right" | "center") {
  2. // ...
  3. }
  4. printText("Hello, world", "left");
  5. printText("G'day, mate", "centre");// 拼写错误
  6. // Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.

当然了,也可以跟非字面量类型联合:

  1. interface Options {
  2. width: number;
  3. }
  4. function configure(x: Options | "auto") {
  5. // ...
  6. }
  7. configure({ width: 100 });
  8. configure("auto");
  9. configure("automatic");
  10. // Argument of type '"automatic"' is not assignable to parameter of type 'Options | "auto"'.

字面量推断

  1. declare function handleRequest(url: string, method: "GET" | "POST"): void;
  2. const req = { url: "https://example.com", method: "GET" };
  3. handleRequest(req.url, req.method);
  4. // Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.

在上面这个例子里,req.method 被推断为 string ,而不是 “GET”,因为在创建 req 和 调用 handleRequest 函数之间,可能还有其他的代码,或许会将 req.method 赋值一个新字符串比如 “Guess” 。所以 TypeScript 就报错了。
有两种方式可以解决:
1、添加一个类型断言改变推断结果:

  1. // Change 1:
  2. const req = { url: "https://example.com", method: "GET" as "GET" };
  3. // Change 2
  4. handleRequest(req.url, req.method as "GET");

2、你也可以使用 as const 把整个对象转为一个类型字面量:

  1. const req = { url: "https://example.com", method: "GET" } as const;
  2. handleRequest(req.url, req.method);

as const 效果跟 const 类似,但是对类型系统而言,它可以确保所有的属性都被赋予一个字面量类型,而不是一个更通用的类型比如 string 或者 number 。

null 和 undefined

  1. const null1: null = null
  2. const undefined1: undefined = undefined

非空断言操作符(后缀 !)

TypeScript 提供了一个特殊的语法,可以在不做任何检查的情况下,从类型中移除 null 和 undefined,这就是在任意表达式后面写上 ! ,这是一个有效的类型断言,表示它的值不可能是 null 或者 undefined:

  1. function liveDangerously(x?: number | null) {
  2. // No error
  3. console.log(x!.toFixed());
  4. }

就像其他的类型断言,这也不会更改任何运行时的行为。重要的事情说一遍,只有当你明确的知道这个值不可能是 null 或者 undefined 时才使用 ! 。

枚举

枚举是 TypeScript 添加的新特性,用于描述一个值可能是多个常量中的一个。不同于大部分的 TypeScript 特性,这并不是一个类型层面的增量,而是会添加到语言和运行时。因为如此,你应该了解下这个特性。但是可以等一等再用,除非你确定要使用它。你可以在枚举类型(opens new window)页面了解更多的信息