TypeScript入门篇

一、什么是 TypeScript?

  • TypeScript 是添加了类型系统的 JavaScript,适用于任何规模的项目。
  • TypeScript 是一门静态类型、弱类型的语言。
  • TypeScript 是完全兼容 JavaScript 的,它不会修改 JavaScript 运行时的特性。
  • TypeScript 可以编译为 JavaScript,然后运行在浏览器、Node.js 等任何能运行 JavaScript 的环境中。
  • TypeScript 拥有很多编译选项,类型检查的严格程度由你决定。
  • TypeScript 可以和 JavaScript 共存,这意味着 JavaScript 项目能够渐进式的迁移到 TypeScript。
  • TypeScript 增强了编辑器(IDE)的功能,提供了代码补全、接口提示、跳转到定义、代码重构等能力。
  • TypeScript 拥有活跃的社区,大多数常用的第三方库都提供了类型声明。
  • TypeScript 与标准同步发展,符合最新的 ECMAScript 标准(stage 3)。

二、为什么用TypeScript?

TypeScript是JavaScript的一个超集,扩展了JavaScript语法,也就是说TypeScript包含了JavaScript

TypeScript = JavaScript + 类型约束 + 高级特性

三、安装TypeScript

1、使用npm(Node.js包管理器)

npm install -g typescript

2、visual studio code

安装VScode的TypeScript插件

四、代码转译

TypeScript 文件后缀使用 .ts 扩展名文件,可通过 tsc index.ts 进行转译,输出一个 index.js 文件,因为浏览器只解析HTML + CSS + JavaScript,所以使用时必须提前把 TS 代码转换成 JavaScript 代码,编译转译 的微小差别在于:

  • 编译是把源码转变成另一种语言
  • 转译是把源码转变另一个相同抽象层级的语言

— 基础部分

原始数据类型

基本数据类型:

字符串类型 (string)

数字类型 (number)

布尔值类型 (boolean)

未定义类型 (undefined)

空值 (null)

符号 (symbol ES6新增) — 符号类型是唯一的并且是不可修改的

大整数(bigInt,ES6 新增)— 使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了JavaScript构造函数 Number 能够表示的安全整数范围。

引用数据类型:

对象类型 (object)

函数 (function)

数组 (array)

一、基本数据类型

// 字符串类型
let name: string = "yuyu";
// 数字类型
let age: number = 512;
// 布尔值类型
let show: boolean = false;
// 未定义类型
let a: undefined = undefined;
// 空值 
let b: null = null;
// 符号
let sym: symbol = Symbol("yy"); 
// 对象类型
let obj: object = {a: 1};

二、复杂数据类型

1、Array 数组

// 数组:元素类型[]
let arr:string[] = ['1','2','3','4'];
// Array<元素类型>
let arr1:Array<number> = [1,2,3,4,5];
// 定义指定对象成员的数组
interface objectItf {
    name:string;
    age:number;
}
let arr2 : objectItf[] = [{
    name:'芋芋',
    age:18
}]

2、元组 tuple


// 元组 -- 对于内部不同类型的数组可以使用元组类型来定义
let tuple:[number,string];
tuple = [18,'芋芋']; // --- res
tuple = ['芋芋',18]; // --- err

— 元组类型只能表示一个已知元素数量和类型的数组,已指定长度;如果一个数组可能有多个元素或者多种类型,就使用 any[] ,非必要情况尽量不使用 any

3、null 和 undefined

null和undefined分别拥有各自的类型,默认情况下它们是所有类型的子类型,即可以赋值给任意的类型。

let str1:string = 'a';
str1 = null; // --- res
str1 = undefined; // --- res
​
let num:number = 1;
num = null;
num = undefined;

可以通过配置tsconfig中 strictNullChecks = true 标记,开启严格模式检查,这种情况下,null和undefined和其他类型是平等关系,只能赋值给any类型和它们各自的类型,还有undefined是可以复制给void类型的。(当为一个函数声明返回类型为void时,函数未显示return的情况下,默认返回的就是undefined)

—– 会有一种情形,比如搭建vite项目,script设置了lang=’ts’时,默认null和undefined不能进行赋值,需要手动把strictNullChecks = false的时候才可以将null和undefined进行赋值,当前,现实场景中,我们不会将null和undefined进行赋值,如果数据初始值需要,我们会通过联合类型将变量添加一个为null/undefined的类型用来赋值。

4、没有类型 void

void 表示没有任何类型,和其他类型是平等关系,不能直接赋值


let a:void;
let b:number = a; // --- err

声明一个void类型的变量意义不大,一般只有在函数没有返回值的时候去声明(比如子传父时,event没有返回值的时候可以声明)

当方法没有返回值的时候将得到undefined,但是我们需要定义成void类型,不能定义undefined类型,不然会报错,因为当函数类型为void时,函数为显示return的情况下,默认返回undefined

5、任意类型 any 和 未知类型 unknown

any 会跳过类型检测,所有值都可以赋值给 any 类型。不推荐类型声明使用 any ,除非实在没有办法的情况下暂时声明,如果都用any类型的话,那么TypeScript将失去它本身的意义,只是用了这个语言,并没有其他实质性的约束。


let a: any = 4;
a = "1"; // --- res
a = false; // --- res

unknownany 一样,所有值都可以赋值给unknown类型。


let a: unknown = "hello";a = 12; // --- res
a = false; // --- res
a = null; // --- res

unknown又有很多方面不同于any类型,任何类型的值都可以赋值给any,同时any类型的值也可以赋值给任何类型,但unknown只是顶层类型,任何类型都可以赋值给unknown,但unknown只能赋值给unknown和any类型,因为只有这两个是顶层类型。


let a: unknown = 1;
let b: any = a; // b: any
​
let c: any = 1;
let d: unknown = a; // d: unknown
​
let e: unknown = 1;
let f: number = a; // 不能将类型“unknown”分配给类型“number”。

不缩小类型就无法对unknown类型进行操作,使其起到了很强的预防性以及安全性,所以我们必须缩小类型,可以使用 typeof类型断言


let a: unknown = 1;
// 直接分配
let b = a.toLocaleLowerCase(); // 类型“unknown”上不存在属性“toLocaleLowerCase”.
​
// typeof
if (typeof a === "string") {
  let c = a.toLocaleLowerCase(); // --- res
}
​
// 类型断言
let d = (a as string).toLocaleLowerCase(); // --- res

anyunknown 的最大区别是, unknown 是 top type (任何类型都是它的 subtype) , 而 any 即是 top type, 又是 bottom type (它是任何类型的 subtype ) ,这导致 any 基本上就是放弃了任何类型检查。

6、永不存在 never

never类型表示永不存在的值的类型。

never类型是任何类型的子类型,可以赋值给任何类型,但是never类型本身没有子类型了,它只能赋值给它本身,及时any类型也不可以赋值给never。


let err: never;
let num: number = 1;
let nev: never;
let any: any;num = err; // --- res
err = 1; // 不能将类型“number”分配给类型“never”。
err = "1"; // 不能将类型“string”分配给类型“never”。
err = any; // 不能将类型“any”分配给类型“never”。err = nev;

值会永不存在的两种情况:

  1. 如果一个函数执行时抛出了异常,那么这个函数永远不存在返回值(因为抛出异常会直接中断程序运行,这使得程序运行不到返回值那一步,即具有不可达的终点,也就永不存在返回了);
  2. 函数中执行无限循环的代码(死循环),使得程序永远无法运行到函数返回值那一步,永不存在返回。

function err(msg: string): never {
  // OK
  throw new Error(msg);
}
​
// 死循环
function infiniteLoop (): never {
  // OK
  while (true) {}
}

never 类型运算

因为 never 类型为底部类型,所以任意类型与 never 交叉都得到 never


type T1 = number & never; // never
type T2 = string & never; // never

对于上面的运算结果,我们可以这样理解:若 type T = T1 & T2,则 T 类型的值可以赋给 T1T2 类型的变量。那么如果与 never 类型交叉,则 T 类型的值可以赋给一个 never 类型的变量,那 T 只能是 never 了。

而任意类型与 never 类型联合,则不会影响原有的类型:


type T3 = number | never; // number
type T4 = string | never; // string

同样,对于上面的运算结果,我们可以这样理解:若 type T = T3 | T4,则 T3T4 类型的值可以赋给 T 类型的变量。由于 never 类型是任何类型的子类型,也可以赋值给任何类型的变量,自然对联合类型不产生影响。

结论:使用 never 类型避免出现新增了联合类型没有对应的实现,目的就是写出类型绝对安全的代码。