TypeScript学习笔记-1 深入理解TS到底是什么?并扫清入门前的常见问题

255 阅读8分钟

TS到底是什么?

我的简单理解是 JavaScript + 类型系统:TS 允许你在变量、函数参数、返回值等地方标注类型(如 numberstringinterface 等),从而在代码运行前捕捉类型错误。

详细说明:

  • JS的超集:所有合法的 JS 代码都是合法的 TS 代码,可以逐步迁移现有 JS 项目。
  • 编译时具有强类型倾向的静态类型语言: 接下来我会详细解释这句话以及相关的概念。

1. 静态类型

静态类型是指在代码编写阶段就确定变量的类型,并在编译阶段进行类型检查。运行时不会检查,因为如果类型不匹配,编译器会直接报错,阻止代码运行,所以能运行的一定是已经无需再检查的代码了。TS 在编译阶段进行类型检查(通过 tsc 编译器或工具链,这些编译工具后面再说)。相反的, JavaScript 是动态类型(运行时才确定类型)。因为js是解释型语言,解释型语言是指在执行时逐行解释源代码的编程语言,是即时编译,不是提前编译,直接在运行的时候编译成机器码了,这个时候才会确定类型,如果类型有错误,比如对number进行字符串方法访问,则会抛出TypeError

2. 编译时

在TS中,编译就是从源码(TS)转成目标代码(JS)的过程。浏览器只理解JS而不理解TS。比如使用tsc把ts文件转换成js文件,这一过程叫做编译。TS 的类型系统(或者说整个所谓的TS)只在代码编译成 JS 这一过程中起作用,如果类型不匹配直接编译失败。编译成功生成的 JS 代码中不会保留类型信息(类型会被擦除)

3. 强类型

不管是强弱类型,都是运行时的定义。对于强类型来说,运行时变量类型固定,除非显式转换,否则不允许隐式类型变化。而JS是弱类型,这点大家应该都知道。TS发生作用是在编译时,因为真正运行时是JS。TS 能阻止开发者写出隐式转换的代码(通过编译时报错)。但是运行时由于是JS(弱类型),所以依然可能会有隐式转换的发生。所以不能直接说TS是强类型,只能说它存在这种倾向,因为这也是TS被发明的目标之一嘛。

混淆点:

  • 混淆点1:在vscode等ide中,写ts代码的时候出现的红色报错,和编译没关系,这是ide内部提供的辅助工具,检测可能的问题。ts的类型系统真正发挥作用是在编译过程中

  • 混淆点2:ts到js是编译没错,但是抛开ts,平时运行js时也是存在编译的,即把js编译成计算机能理解的机器码。

  • 混淆点3:静态类型和强类型的区别,前者在于在运行前(即编译时)有无类型检查,后者在于运行时类型之间是否可以隐式转换

入门上手

深入学习前简单体验一下

类型标注

注意首字母小写,大写开头的是包装类型,此处先不提。

let v1:number = 1;
let v2:string = "hello";
let v3:boolean = true;
let v4:string;
v4 = "world";
// null和undefined也是一种类型,只要有了类型标注,有且只有一个值
let nullVar:null = null;
let undefinedVar:undefined = undefined;

类型推断

有时可以省略类型声明,因为TS会非常智能地进行类型推断。

let v5 = 10;
let v5 = "aaa"; //报错,因为TS推断出v5是数值类型,所以不能再赋成别的类型


// 如果还希望想js一样,赋值任意类型,可以使用any标注
let v8:any = 123;
v8 = "hello";
v8 = true;
v8 = null;

ES版本影响

TS在编译的时候可以指定要编译成的js代码版本,不同的版本中JS的语法是不同的。 BigInt是ES2020的新语法,想要代码合法,必须编译成ES2020,如果编译成低版本的js,代码会报错。

let v11 = Symbol("a");//ES6
let v12 = 123n;//ES2020

字面量类型

// 因为const声明的变量不能更改,所以默认的类型就是常量字面量类型
const v13 = 123;//推断为字面量123类型


// 字面量类型,就只能是指定的值
const v14:123 = 45;//报错
const v15:"hello" = "hello";

初步安装与运行

在终端输入pnpm i -g typescript 进行全局安装即可

在nodejs环境或者浏览器环境中 直接执行如下代码会报错,因为不管是浏览器还是node,他们只认识js,而冒号后面加类型是TS的语法,他们不认识。

let a: undefined = undefined;//用node或者浏览器中运行会报错

需要使用tsc工具现将ts编译成为js,直接在终端输入tsc 文件名.ts即可,然后运行js文件。

常见问题与配置(非常重要)

变量冲突

image.png

发生条件:

  1. 没有配置tsconfig.json文件
  2. 没有模块化。

发生原因:

  1. 这个错误是因为在 非模块化 的 TypeScript 文件中(即没有 import/export 语句),TypeScript 默认会将代码视为在 全局作用域 中运行。
  2. TypeScript 默认会包含 DOM 类型定义(除非在tsconfig.json文件中显式配置 "lib": ["esnext"]
  3. DOM类型定义是定义在全局的,含有windowdocumentHTMLElement 等全局对象。而name变量实际上就是全局对象window中的变量。由于当前ts文件没有模块化导出,所以报错了。

解决办法

  1. 模块化导出。

image.png 可以看到报错消失了。因为name变成了当前模块中的局部变量。

  1. 增加tsconfig.json配置文件,进行配置。下面我再展开讲。

配置文件 tsconfig.json

我讲几个重要的配置项及其问题

  1. compilerOptions: 配置编译规则
  2. lib:要引用的库(类型声明文件)。如果不写lib,引用的库默认由target中的ES版本决定。如果下面不配 lib 属性,那么默认引用target中写的ES2017库。这会导致ts文件中无法使用其他库里的方法及变量
  3. outDir:编译后的JS文件存放的目录。如果不设置,默认和被编译的ts文件处于同一目录下
  4. include:哪些目录下的哪些文件需要编译。

我将在在如下代码的注释中进一步说明其他问题。

{
  "compilerOptions": {
    "target": "ES2017", // 编译目标JS版本
    "lib":["ES2017","DOM","DOM.Iterable"], // 需要引用的库
    //"lib":["ES2017"],如果这样写,即使不用模块化导出,上面变量冲突情况中的name也不会冲突了,因为现在只有ES2017这个库了,而这个库中不存在全局变量name。
    //而这又会导致,console.log等浏览器全局方法也没了(在DOM库中),如果在ts文件中使用这些方法,会报错。
    "outDir": "./dist" // 指定输出目录为dist文件夹
  },
  "include": ["src/**/*.ts"] // 指定需要编译的文件为src文件夹下的所有ts文件
}

另外还有一个注意点,有了配置文件tsconfig.json后,编译命令 tsc 文件名.后缀就不要去指定文件了,这样会对该文件执行默认的编译规则,指定的outDir就失效了。而是要直接运行tsc,这个命令会去编译include中指定的所有TS文件,并将编译后的JS文件放入outDir中指定的dist目录下。

先在src文件夹下创建2个ts文件,并在根目录下配置tsconfig.json文件,在根目录的控制台下输入tsc后,所有文件如下:

image.png

ts-node与nodemon

当我们用tsc命令将ts文件编译成js文件后,在node环境中,仍需要在控制台上运行node 文件名.js,其实有点麻烦。

ts-node命令可以在内存中直接编译JS并直接输出结果。安装命令:npm i -g ts-node

  • 先前流程为:tsc->node 文件名.js->控制台输出结果。且输出目录下出现编译后的JS文件。

  • ts-node后的流程:ts-node 文件名.js->控制台输出结果。且输出目录下无JS文件产生。因为该命令在内存中直接编译并运行了JS。

但是上述流程依然需要我们每次修改完文件后都要在控制台上运行一遍命令,能否像dev server那样每次保存文件后立刻可以出现结果呢?--使用nodemon。安装命令:npm i -g nodemon

运行命令nodemon --exec ts-node 指定文件的路径.ts。之后再修改该被监听文件,只需保存即可立刻输出结果,比前面两种方便多了。至于这条命令的详细参数(exec等)这里就不讨论了。