TypeScript 教程

1. TypeScript 概述

1.1 TypeScript 是什么

TypeScript 是一门基于 JavaScript 的编程语言,它是具有类型系统的 JavaScript,是一种解决 JavaScript 缺点的编程语言。

// 所有的 JavaScript 代码都是合法的 TypeScript 代码。
var a = 10;
function fn () {}
if (true) {}
for (var i = 0; i < 10; i++) {}
// TypeScript 是静态类型编程语言,即编译期间进行类型检查,变量、参数、返回值等都必须有固定的类型。
let x: number = 10;
x = 20;  // ✅
x = "a"; // ❎

TypeScript 不能在浏览器环境或者 Node 环境直接运行,它在执行前需要先被编译为 JavaScript。

// TypeScript
let isLogin: boolean = false
// Javascript
let isLogin = false;

TypeScript 提供的类型系统只应用在开发阶段,只有在开发阶段开发者才需要借助它编写出更加健壮的程序。


TypeScript 由微软发布,第一个正式版的发布时间为是2013年6月19日。

1.2 类型系统带来的好处

通过静态类型检查可以让开发者在编译时就能发现错误而不是在代码运行时,而且静态类型系统还大大增强了代码的可读性以及可维护性。

类型系统为编辑器带来了更加精准的代码提示,以此来提升开发人员的编程体验。

app.get("/", function (req, res) {
  res.
    send
    sendDate
    sendfile
    sendFile
    sendStatus
});

在声明变量时明确告知编译器它的类型,编译器就知道该变量可以调用哪些属性和方法,当开发者调用了错误的属性或方法时,编译器会及时给出提示。

var name: string = "张三";
name.toFixed(2); // 属性"toFixed"在类型"string"上不存在

在声明函数时明确告知编译器参数的类型,当开发者调用该函数时如果传递的参数的类型不正确,编译器会及时给出提示。

function sum(x: number, y: number) {}
sum(10, "a"); // 类型"string"的参数不能赋给类型"number"的参数。

在声明函数时明确告知编译器返回值的类型,当开发者返回的值的类型错误时,编译器会及时给出提示。

function sayHello(): string {
  return 100; // 不能将类型"number"分配给类型"string"。
}

在声明对象时告知编译器该对象下有哪些属性,当开发者访问了对象中不存在的属性时,编译器会及时给出提示。

const person = { name: "张三" };
person.age; // 类型"{name: string}"上不存在属性"age"。

类型系统使代码变得可预测,能够让开发者更早的发现代码存在的问题和潜在问题。

for (var i = 0; i < 5; i++) { }
console.log(i); // 编译器会捕获到错误: cannot find name "i"

当重复声明同名变量时,编译器会立即给出提示。

let colors = ["red", "green", "blue"];
// 无法重新声明块范围变量"colors"。
let colors = 10;

2. TypeScript 初体验

2.1 第一个 TypeScript 应用

需求:向服务端发送请求获取 ID 为 1 的任务对象。
目标:将 TypeScript 编译为 JavaScript。


  • 安装 TypeScript 编译器,用于将 TypeScript 代码编译为 JavaScript 代码
  • 安装 axios 库,用于发送请求
  • 新建 index.ts 文件,用于编写代码
  • 将 TypeScript 代码编译为 JavaScript 代码并执行

第一步:安装 TypeScript 编译器,用于将 TypeScript 代码编译为 JavaScript 代码

-g

# 全局安装 typescript 编译器
npm install -g typescript
# 通过查看 typescript 编译器版本验证编译器是否安装成功
tsc -version

第二步:安装 axios 库,用于发送请求。

# 安装 axios 用于发送请求
npm install axios

第三步:新建 index.ts 文件用于编写代码,TypeScript 程序文件的后缀名为 .ts

import axios from "axios";

axios.get("https://jsonplaceholder.typicode.com/todos/1").then((response) => {
  console.log(response.data);
});

第四步:将 TypeScript 代码编译为 JavaScript 代码并执行。

# 编译 index.ts 文件, 编译后在同级目录下会多出 index.js 文件, 该文件存储的就是编译后的 JavaScript 代码
tsc index.ts
# 执行 JavaScript 代码
node index.js

2.2 优化工作流

目标:监控 TypeScript 文件的变化,实现自动编译、自动执行代码


  • 安装 nodemon、ts-node
  • 创建应用启动脚本
  • 通过应用启动脚本启动应用

# nodemon: 监听文件的变化, 当 TypeScript 文件内容发生变动后调用 ts-node
# ts-node: 将 TypeScript 编译为 JavaScript 并执行
npm install -g nodemon ts-node
// package.json
"scripts": {
  "start": "nodemon index.ts"
},
npm start

tsc 与 ts-node 的主要区别在于 tsc 根据 tsconfig 编译所有文件,ts-node 会从入口文件开始,并根据模块关系逐步转译文件。

2.3 体验类型带来的好处

需求:将任务ID、任务名称、任务是否完成分别输出到控制台中。

import axios from "axios";

axios.get("https://jsonplaceholder.typicode.com/todos/1").then((response) => {
  const todo = response.data;
  const id = todo.ID;
  const title = todo.Title;
  const finished = todo.finished;
  console.log(`
    任务的ID是: ${id},
    任务的名称是: ${title},
    任务是否完成: ${finished}
  `);
});

以上代码执行后,输出的结果都是 undefined,发生了什么?

任务的ID是: undefined,
任务的名称是: undefined,
任务是否完成: undefined

通过查看得知,任务 ID 对应的属性名称是 id,任务名称对应的属性名称是 title,任务是否完成对应的属性名称是 completed,原来是属性名称写错了。

目前的问题是在书写代码的过程中并没有任何的错误提示,只有代码运行以后开发者才能够知道代码中存在错误,这个问题应该怎么解决呢?

显式告知 TypeScript 编译器 response.data 中存储的数据的类型,编译器会实时检测你写的代码是否符合类型上的要求。

以下代码展示的是通过 TypeScript 约束对象中可以存在的属性,当访问了不存在的属性时编译器会实时进行提示。

import axios from "axios";

// interface 意为接口, 可以约束对象中可以有哪些属性, 约束对象中属性的类型 
interface Todo {
  id: number;
  title: string;
  completed: boolean;
}

axios.get("https://jsonplaceholder.typicode.com/todos/1").then((response) => {
  const todo = response.data as Todo;
  const id = todo.ID; // 属性"ID"在类型"Todo"上不存在。你是否指的是"id"?
  const title = todo.Title; // 属性"Title"在类型“Todo"上不存在。你是否指的是"title"?
  const finished = todo.finished; // 属性"finished"在类型"Todo"上不存在。
  console.log(`
    任务的ID是: ${id}, 
    任务的名称是: ${title}, 
    任务是否结束: ${finished}
  `);
});

以下代码展示的是通过 TypeScript 约束函数参数的类型,调用函数时如果传入的参数类型错误,编译器会实时进行提示。

import axios from "axios";

interface Todo {
  id: number;
  title: string;
  completed: boolean;
}

axios.get("https://jsonplaceholder.typicode.com/todos/1").then((response) => {
  const todo = response.data as Todo;
  const id = todo.id;
  const title = todo.title;
  const completed = todo.completed;
  logTodo(title, id, completed); // 类型"string"的参数不能赋给类型"number"的参数。
});

function logTodo(id: number, title: string, completed: boolean) {
  console.log(`
  任务的ID是: ${id},
  任务的名称是: ${title},
  任务是否结束: ${completed}
`);
}

3. TypeScript 基础类型

3.1 概述

以下表格中列出的所有类型在 TypeScript 中都是支持的。

JavaScriptTypeScript
numberany
stringunknow
booleannever
nullenum
undefinedtuple
object
array

3.2 基本数据类型

在 TypeScript 中,开发者可以通过类型注释对变量的类型进行标注。

// 数值类型
// :number 类型注释
let apples: number = 5;
// 字符串类型
let speed: string = "fast";
// 布尔值布尔
let hasName: boolean = true;
// TS2322: Type 'string' is not assignable to type 'number'
// 类型 'string' 不能分配给类型 'number'
apples = "5";    // ❎

// TS2322: Type 'number' is not assignable to type 'string'.
// 类型 'number' 不能分配给类型 'string'
speed = 120;     // ❎

// TS2322: Type 'string' is not assignable to type 'boolean'.
// 类型 'string' 不能分类给类型 'boolean'
hasName = "yes"; // ❎

3.3 any 类型

// any 类型
// 变量中可以存储任何数据类型的值
let anything: any = "Hello TypeScript";
anything = true;           // ✅
anything = 3.14;           // ✅
anything = function () {}; // ✅
anything = null;           // ✅

3.4 unknown 类型

unknow 是严格的 any 类型,在对 unknown 类型的变量执行操作之前必须先确定它的类型。

let anything: unknown = "Hello TypeScript";
anything = true;            // ✅
anything = 3.14;            // ✅
anything = function () {};  // ✅

// TS2571: Object is of type 'unknown'. ❎
// console.log(anything.length);

if (typeof anything === "number") {
  anything.toFixed();
} else if (typeof anything === "string") {
  anything.toUpperCase();
} else if (typeof anything === "function") {
  anything();
}

3.5 数组 Array

// 在没有为数组变量标注类型时, 变量的初始值又是一个空数组
// 此时该数组中可以存储任何类型的值
// 虽然写法正确, 但丢失了 TypeScript 类型检查功能
// let colors = never[];
let colors = [];
// 字符串数组
let colors: string[] = ["red", "green", "blue"];
// 数值数组
let numbers: number[] = [100, 200, 300];
// 布尔数组
let bools: boolean[] = [true, true, false];
// 二维数组
const carMakers: string[][] = [["宝马", "比亚迪"]];
// let colors: string[]
// item: string
colors.forEach((item) => {});
// a: string
let a = colors[0];

3.6 元组 Tuples

元组可以按照顺序约束数组中每个下标对应的数据的类型。

[string, number, boolean] => [“a”, 100, false]

观察下列代码中存在的问题。

在 employee 数组中我们约定下标为0的位置存储员工姓名,下标为1的位置存储员工的年龄。

let employee = ["张三", 20];
employee[0] = 30;
employee[1] = "李四";

以上代码中存在的问题是 employee 数组中的元素没有被类型系统约束,导致在修改元素时没有任何错误提示。

元组是 TypeScript 引入的一种新数据类型,它像数组一样工作但是有一些额外的限制:元组中元素个数是固定,元组中元素类型已知。