TypeScript基础
TypeScript 的基础概要
简介:
- TypeScript 由微软开发,是基于 JavaScript 的一个
扩展语言
,它包含了 JavaScript 的所有内容,所以它被称为 JavaScript 的超集
。 - TypeScript 增加了
静态类型检测
、接口
、泛型
等诸多现代化开发特性,更适合大型项目
的开发。 - TypeScript 不能直接运行在浏览器中,它需要经过
编译后
转为JavaScript
才能在浏览器中运行。 - 由于方便书写,下文中的一系列
TypeScript
我将都简称为TS
。
- TypeScript 由微软开发,是基于 JavaScript 的一个
为何需要:
JavaScript 的出现
- 由 JavaScript 最初的浏览器脚本语言为引,在后续的发展中,JavaScript 能做的事情越来越多,截至今日亦可以开发
全栈项目
。 - 随着 JavaScript 的流行和使用,不同项目也随着应用场景的开发堆叠越来越多的 JavaScript 代码,这使得不同的开发人员在此基础上开发维护显得异常艰难。
- 由 JavaScript 最初的浏览器脚本语言为引,在后续的发展中,JavaScript 能做的事情越来越多,截至今日亦可以开发
JavaScript 的困扰
不清楚的数据类型
const str = "a";
str(); // 类型错误,显示str不是一个function有问题的逻辑交互
const value = Math.floor(Math.random() * 100) % 2 ? "偶数" : "奇数";
// 逻辑错误,重叠
if (value !== "奇数") {
console.log("不等于奇数");
} else if (value === "偶数") {
console.log("它是偶数");
}属性的不安全访问
const person = {
name: "Alan",
age: 18,
};
console.log(
`我是${person.name},今年${person.age},身高${person.height}`
); // 警告,person不存在height属性代码词汇的拼写错误
const java = "java 是一门后端开发语言";
console.log(jvav); // 低级错误,手快敲错
静态类型检查
TS
会在代码运行前进行检查,能够发现文件中代码的错误和不合理之处,减小运行时出现的 bug 几率,这种行为被称为静态类型检查
。- 虽然在同样的模块中,TS 的代码量会比 JavaScript 的多得多,但是基于 TS 带来的各种类型规范,恰好能够使得 TS 代码中的代码结构更加清晰,清晰明了的类型也使得开发者在后期的项目维护中更得心应手。
TS 编译
- 终端命令行编译
- 安装 typescript
npm install -g typescript
- 使用 tsc 命令并指定编译文件
tsc /path/to/ts/file
- 安装 typescript
- 自动化编译
- 同理也是使用
tsc
进行编译,但区别是我们需要用到额外的参数进行编译 - 初始化
tsconfig.json
配置文件,具体参数请参考官方文档tsc --init
- 终端运行指令,实现 ts 文件热更新编译 JavaScript
tsc --watch /path/to/ts/file
- 同理也是使用
类型声明
- 变量或函数形参类型声明:
let str: string; |
- 字面量类型:字面量类型是一种类型,它表示某个特定的值,而不是某一类值。
let str: "hello"; |
类型推论
- 在 TS 里,有些没有明确指出类型的地方,它会自动
类型推论
帮助使用者提供正确的类型。
let a = 23; |
TypeScript 中的基础类型
小写类型和首字母大写类型含义不一样,不能一概使用
小写(
string
)是原始类型
,首字母大写(String
)是包装对象类型
我们基本不推荐使用
包装对象类型
进行类型声明- 它是
可变的引用类型
,具有额外的属性和方法,会引入不必要的复杂性,例如原型链上的不确定性属性带来的影响 - 使用包装对象会
创建额外的对象
,带来一定程度上的内存消耗
和性能开销
,使用原始类型
相对来说会更轻量、可读性更强、效率更高
- 增加代码复杂性,不利于代码的维护
- 自动装箱 :
- 此概念是 TS 中所存在的,它解释了
包装对象类型
存在的意义
- 此概念是 TS 中所存在的,它解释了
const str = "this is a string"; // 原始类型
console.log(str.length);
// mock js engin compiler
const str = "this is a string";
const size = (function () {
// 自动装箱,将会创建一个临时的包装对象包装原始类型值,例如这里的String
let tempObject = new String(str);
// 访问包装对象上的属性(prototype),如length
let tempValue = tempObject.length;
// 销毁临时对象,返回对应值
// js引擎会自动回收对象销毁(V8垃圾回收),用户手动回收释放内存可以设置属性为null,tempObject = null
return tempValue;
})();
console.log(size);- 它是
JavaScript 标准内置类型
string/String
(略)
number/Number
(略)
boolean/Boolean
(略)
bigInt
(略)
symbol
(略)
undefined
(略)
null
(略)
object/Object
较特殊,包含 Array,Function,Date,Error 等诸多对象
无论是小写
object
还是大写Object
,在实际开发中都使用较少,因为其包含范围比较广object
小写含义:所有非原始类型
,包含对象
、数组
、函数
等,范围极其广泛对象类型声明
let obj: object;
obj = []; // ✅
obj = {}; // ✅
obj = () => {}; // ✅
obj = new Error("123");
class Mock {} // ✅
obj = new Mock(); // ✅
// 如果将原始类型赋值给object
obj = 123; // ❌,警告,不能将类型number分配给object
obj = "123"; // ❌,警告,不能将类型string分配给object
obj = true; // ❌,警告,不能将类型boolean分配给object
obj = undefined; // ❌,警告,不能将类型undefined分配给object
obj = null; // ❌,警告,不能将类型null分配给object- 数组类型声明
// 两种书写格式皆可
let arr: number[]; // ✅,数组元素皆为number
arr = [1, 2, 3]; // ✅
arr = [1, 2, "123"]; // ❌
let arr2: Array<string>; // ✅泛型,数组元素皆为string- 函数类型声明
// 函数类型声明表达式, => 在这里是表达分隔符
// 特殊情况下的函数声明(type类型声明),会对函数的返回值有一定影响,具体可以参考type目录中的特殊情况
let computed: (x: number, b: number) => number;
computed = (a, b) => a + b;
computed(1, 2);
TypeScript 自带类型
tuple
元组(tuple)
是一种特殊的数组类型
,用于精确描述一组值的类型。可以存储固定数量的元素,每个元素的类型是已知的且可以不同
。有时候可以使用?
代表可选值。
let arr: [number, string, boolean, ?number]; |
enum
枚举(enum)
可以定义一组命名常量
,作用在于增强代码的可读性,方便代码的维护
数字枚举(具有递增性),默认从 0 开始,如果更改枚举 number 值,将会自动重新从小到大排序
enum Actions { |
字符串枚举
- 不使用枚举
function getSkills(skill) { |
- 使用枚举后
enum Skills { |
常量枚举(优化手段):它是一种特殊枚举值,使用
const
关键字定义,在编译时会被内联
,可以避免产生额外的代码
编译内联
指的是 TS 在编译时,会将枚举成员的引用
替换为其对应的实际值
,而不是生成额外的枚举对象,这样可以实际减少编译后js代码
的输出,提高运行时性能
- 当使用了常量枚举声明后,在使用时不引用具体的枚举值,TS 会警告
"const" 枚举仅可在属性、索引访问表达式、导入声明的右侧、导出分配或类型查询中使用。
- 默认情况下的 ts 编译后枚举结构
enum Actions { |
- 使用常量枚举后的结构
const enum Actions { |
any
- 不推荐使用 : TS 将会忽略类型检查,允许用户随意更改属性/方法的输入输出,即所谓的“AnyScript”,令人贻笑大方。
void
- 常用于函数返回值的声明,它是
没有任何意义
的返回值,一个void
类型的变量没有什么大用,因为你只能为它赋予undefined
。 - 在 Js 中,函数默认情况下会返回
undefined
,但是在 TS 中会给没有定义具体返回类型(默认返回值)的函数
推导出void
,所以很多情况下可以忽略void
的类型声明, - 注意:
void
作为函数返回值
时不应当被使用者依赖其返回值
进行任何操作,因为前面也说了,它是没有任何意义的返回值
- 相较于 Js 中的
undefined
,TS 中的void
是更加严格的一种表示空
的类型,且语义
上也表示不关心该返回值
,它的返回值都不应该被使用或依赖(重要的事情再说一遍!)
type undef = undefined; |
never
never
类型表示的是那些永不存在的值
的类型。用作于函数的返回值
或者某种情况下由 TS 检查自动推断
出来。通常也用作于完整性/边界性检查。
// 某种条件语句,让ts主动推断 |
- 是那些总是会
抛出异常
或根本就不会有返回值
的函数表达式
或箭头函数表达式
的返回值类型(简称:函数返回值类型)
// 符合never类型的情况,在函数中的表现主要有以下几种: |
unknown
语义化理解,
未知
类型。可以理解为一个安全的any
类型,TS 会对该类型的值进行检查,适用于不确定数据的具体类型。与
any
比较:- 强制类型检查
// any 类型,不管变量被如何更改,ts都不会进行类型检查
let a: any;
a = 1;
a = "123";
a = false;
a = {};
// unknown 类型,与any一样可以随意赋值,但是无法将该unknown类型的变量赋值给声明具体类型的变量,因为TS会对其进行类型检查
let num: number;
let un: unknown;
un = 123;
un = "123";
un = false;
num = un; // ❌,TS将会警告提醒,不能将unknown类型分配给number
// 可以限制条件使得ts检查通过,代价是不必要的代码量
if (typeof un === "number") {
num = un;
}
// 甚至可以使用`断言(as)`指定类型
num = un as number;
//or
num = <number>un; // 断言的另一种写法- 读取任何属性、对象原型方法都会警告
// any类型下的所有属性都不会警告,因为ts不检查
let an: any;
an = 123;
an = "3213";
an.toUpperCase();
an.bool = false;
// unknown类型则会受到一定限制
let un: unknown;
un = "123";
un.toLowerCase(); // ❌,ts警告,un的属性是未知的
un.a = 1; // ❌,皆会警告
un.b = "213"; // ❌,皆会警告元组
,
用于定义类型的关键字
type
type
能为任意类型创建别名,让代码更加简洁优雅、可读性更强,且也能更方便的进行类型复用和扩展
- 类型别名:
type newString = string; |
- 联合类型:高级类型用法,表示一个值可以为多种不同类型之一
type commonType = number | string | boolean; |
- 交叉类型:可以将多个类型合并成一个,合并后的类型拥有所有被合并类型的成员。常用于对象类型
type Animal = { |
- 不应该在交叉类型中使用基本数据类型,因为 TS 无法推断出你的值,默认返回
never
type AnyValue = string & number; |
特殊情况
通过
类型声明
限制函数返回值为void
时,TS 将不会严格要求函数返回值
为空
type VoidFn = () => void; |
为什么会这样?参考官方文档
- 官网说法是让以下的代码能够成立且有效:
let arr: number[] = [1, 2, 3, 4, 5]; |
interface
它是
定义结构
的一种方式,主要作用是为类、对象、函数
等规定一种契约
,有效确保代码的一致性和类型安全。interface
只能定义格式
,不能包含任何实现。
定义类结构
interface PostsInterface { |
定义对象结构
interface PostInterface { |
定义函数结构
interface FnInterface { |
接口之间的继承
interface PersonInterface { |
接口自动合并
interface PersonInterface { |
接口的使用场景:
- 定义对象格式:描述数据结构,配置对象等
- 类的契约规范:规定一个类具体的属性和方法
- 自动合并:用于扩展第三方库的类型
type
和interface
的区别
- 相同点:
type
和interface
都可以拿来定义对象结构
,两种方式在不同场景皆可互换使用。- 不同点:
- `type`:可以定义类型别名、联合类型、交叉类型,不支持`继承`和`自动合并`。
- `interface`:更专注于定义`对象`和`类`的结构,支持`继承`和`自动合并`。
泛型
泛型允许我们在定义函数、类或接口时,使用类型参数来表示未指定的类型,这些参数在具体使用时,才被指定具体的类型,泛型可以让同一段代码适配多种类型,同时保持类型的安全性。
- 注:
T
只是一种规范名缩写而已(Type),表明区域位置的类型
function fn<T>(s: T): T { |
多种泛型
- 支持多个泛型一起使用
function edit<C, T>(s: C, dosomething: T): C | T { |
泛型接口
- 拓展
interface
声明目标的类型,自由度更高,灵活性更强
interface MessageInterface<T> { |
泛型类
- 与接口用法差不多
// 基本用法 |
类型声明文件
类型声明文件是 TS 中的一种特殊文件,通常以
.d.ts
作为文件扩展名。主要作用在于为现有的JavaScript代码提供类型信息
,让 TS 能够在使用相关的JavaScript库时或者模块
时能够得到类型检查和提示
默认在
TS 模块中引入 Js 文件
时,TS 将会警告提醒无法识别导入内容demo
- utils.js
export const add = (x, y) => x + y;
export const mul = (x, y) => x * y;- b.ts
import { add, mul } from "./utils.js"; // TS警告提示,无法找到模块“./utils.js”的声明文件。xxx隐式拥有 "any" 类型。
console.log(add(1, 2));
console.log(mul(2, 3));创建具名的
.d.ts
类型文件(指定的文件名称,这里我的是utils.js
)- 创建
utils.d.ts
declare const add = (a: number, b: number) => number;
declare const mul = (a: number, b: number) => number;
export { add, mul };- 创建完成后,需要关闭编译器重新打开让 TS 编译,就可以让你的 TS 重新识别你的模块类型
- 创建
三斜线指令
- 三斜线指令是包含
单个 XML 标签的单行注释
。 注释的内容会作为编译器指令使用
。 - 三斜线指令
仅可
放在包含它的文件的最顶端
。 一个三斜线指令的前面只能出现单行或多行注释
,这包括其它的三斜线指令。 如果它们出现在一个语句或声明
之后,那么它们会被当做普通的单行注释
,并且不具有特殊的涵义。 - 三斜线引用告诉编译器在
编译过程中要引入的额外的文件
。
/// <reference path="..." />
语法:
path
指定文件名- 当使用
--out
或--outFile
时,它也可以做为调整输出内容顺序的一种方法。 文件在输出文件内容中的位置与经过预处理后的输入顺序一致。
/// <reference path="xxx.d.ts" />
- 当使用
预处理输入文件
- 编译器会对
输入文件
进行预处理
来解析所有三斜线引用指令
。 在这个过程中,额外的文件
会加到编译过程
中。 - 这个过程会以一些
根文件
开始; 它们是在命令行中指定的文件或是在tsconfig.json
中的"files"
列表里的文件。 这些根文件按指定的顺序
进行预处理。 在一个文件被加入列表前,它包含的所有三斜线引用都要被处理,还有它们包含的目标。 三斜线引用以它们在文件里出现的顺序,使用深度优先的方式解析。 - 一个
三斜线引用路径
是相对于包含它的文件
的,如果不是根文件
。
- 编译器会对
错误
引用不存在的文件会报错。 一个文件用三斜线指令引用自己会报错。
使用
--noResolve
如果指定了
--noResolve
编译选项,三斜线引用会被忽略
;它们不会增加新文件
,也不会改变给定文件的顺序
。
///<reference types="..." />
- 这个指令是用来声明依赖的; 一个
/// <reference types="modulename" />
指令则声明了对某个包
的依赖
。- 例如,把
/// <reference types="node" />
引入到声明文件,表明这个文件使用了@types/node/index.d.ts
里面声明的名字; 并且,这个包
需要在编译阶段与声明文件一起被包含
进来。- 注:仅当在你需要写一个
d.ts文件
时才使用这个指令。
///<reference no-default-lib="true" />
- 这个指令把一个文件标记成
默认库
。 你会在lib.d.ts
文件和它不同的变体的顶端
看到这个注释。- 这个指令告诉编译器在编译过程中
不要包含这个默认库
(比如,lib.d.ts)。 这与在命令行上使用--noLib
相似。- 注意,当传递了
--skipDefaultLibCheck
时,编译器只会忽略检查
带有/// <reference no-default-lib="true"/>
的文件。
///<amd-module />
- 默认情况下生成的
AMD模块
都是匿名
的。 但是,当一些工具需要处理生成的模块时会产生问题,比如 r.js。
amd-module
指令允许给编译器传入一个可选的模块名
///<amd-module name='Demo'/> |
- 会将
Demo
传入到AMD define函数
里
define("Demo", ["require", "exports"], function (require, exports) { |
内容参考来自官方中文文档,英文在这:TypeScript Doc