01.TypeScript基础
1.1 开发环境
官网下载
创建项目
初始化第一个TypeScript项目
新增一个"first-ts-project"文件夹
进入"first-ts-project"文件夹以后执行,就能在文件夹下自动出现的一个package.json文件
然后接着执行以下命令安装typescript最新稳定版
创建tsconfig.json
方法1:利用VScode编辑器
-
先创建一个
index.ts文件,用VScode打开 -
点击VScode底部TypeScript的版本号
-
在弹出来的弹出框点击【创建tsconfg】即可
会出现提示创建tsconfig.js的选项
点击创建,便自动创建好了
创建完tsconfig.json后,想要在vscode中添加参数,只要打上""就能出现提示,鼠标放在参数上还会有提示
TS Playground
方法2:利用命令行创建
通过这种方法创建tsconfig.json包含所有编译器参数以及参数说明
1.2预备知识
TypeScript与JavaScript的关系
TypeScript是JavaScript的超集
TypeScript提供所有JavaScript特性。并在其上层增加了TypeScript的类型系统
这个类型系统被设计为可选的。这意味着所有合法的JavaScript代码都是合法的TypeScript代码。
TS的编译过程
我们用C++的编译过程与TS的编译过程进行对比
从图中可以发现无论TS文件有没有错误,都会生成js文件。如果TS文件有错误,js文件在运行时也会报错。
类型系统
结构类型系统
结构类型系统(Structural Type System):通过类型的实际结构确定两个类型是否相等或者兼容(TypeScript,Haskell,Ocaml,Go)
可以看这个例子:创建了一个Foo类型的a,直接赋值给Bar类型的b;
这样是没有问题的,因为他们类中有相同的结构
名义类型系统
名义类型系统(Nominal Type System):通过类型的名称确定两个类型是否相等(C,C++,Java,C#,Rust...)
同样的例子在名义类型系统中就不行,因为他们的类型名不一样。
类型注解
类型与集合
每一个类型对应的集合
空集 never = {}
单元素集合 Null = {null} Undefined = {undefined} Literal Type(字面量类型,"a",1,true)
有限集合 Boolean = { true, false }
无限集合 String = { 'a', 'b',... } Symbol = { Symbol(...),...} BigInt = {..., -1n, 0n, 1n, ...} Number = {-lnfinity,-(2^53 - 1),...,0,,,+2^53 -1,Infinity}和NaN
全集
类型交叉与合并
类型别名
在JS中,可以用let、const、var来声明变量
在TS中,可以使用type为类型声明别名
let ID = 9527;
const PI = 3.14;
var myPI = PI;
// 在ts中 类型名 与 变量名 不冲突,用到类型的时候代表的是类型,用到变量的时候代表的是变量
type ID = string;
type User = {
id:ID;
name:string;
}
复制代码
但是type与let一样有作用域
在同一域中声明会报错
但是在不同域中声明就不会报错
类型的拓宽于收窄
类型拓宽(Type Widening)
当你把用字面量赋值给let、var变量时,TS不用字面量类型作为该变量的类型。而是从字面量类型拓宽到相应的更宽泛的类型。
类型收窄(Type Narrowing)
在某些情况写,TS可以更加确定变量的类型,此时它会将变量类型收窄。
TS试图在类型确定性与灵活性之间取得平衡
TS提供一系列方法来帮助收窄类型,以提高类型的确定性:
null check, as const, instanceof, typeof, 属性检查, Tagged Union , 用户类型守卫, 代码流分析
当使用let var声明变量时,TS认为变量会在未来发生变化,所以将类型推断为相对宽泛的类型
当使用const声明变量时,TS知道常量是不会发生变化的,会将类型推断最窄的字面量类型
值空间与类型空间
类型空间是由TS的编译器tsc创建的,里面存在各种类型。tsc会根据代码实例化各种类型。
值空间是后JSEngine 创建的 里面存在各种运行时的值。
TS的语法结构会给两个空间引入值。
type和interface会为类型空间引入相关的类型
const let var 会为值空间引入相关的变量或者常量
enum class namespace 会为类型空间引入类型,为值空间引入值
如果namespace只包含类型声明,不会产生js代码,不会引入变量
如何判断符号在哪个空间?
-
转译后消失的符号 -> 类型空间
-
作为类型注解、别名的符号 -> 类型空间
type T= typeof Person; const p:Person
-
类型断言后的符号 -> 类型空间
target as/is HTMLElement
-
const let var 后面的符号 ->值空间
-
class enum namespace 后面的符号 -> 值空间 + 类型空间
还有一些操作符在两个空间都存在,但是表达的含义完全不同。
typeof
typeof 后面的表达式只能是变量,不能是类型
在值空间,typeof返回后面表达式对应的JavaScript类型的字符串表示:
('string','number','bigint','boolean','symbol','undefined','object','function')
在类型空间,typeof返回标识符对应的TypeScript类型
[]索引访问操作符
在值空间,val[field]或val.field返回val对应属性的值
在类型空间,Type[T]返回对应T的TS类型
this关键字
在值空间,this指向比较复杂
在类型空间,this可以作为类方法的返回值来实现链式调用
& | 运算符
在值空间表示”按位与“和 ”按位或“
在类型空间表示类型的交叉和联合
const
在值空间用来声明常量
在类型空间与as连用,即”as const“ 常量断言,收窄类型
看下面这个例子
不加as const 被推断成number类型
下面是加as const的结果
externds
在值空间中用于定义子类
class A externds B{}
复制代码
在类型空间中用来进行类型约束或接口继承
T externds number
interface A externds B
复制代码
in
在值空间中用于for循环 和 判断属性是否存在
在类型空间中用于映射类型的key的声明
[K in T]
复制代码
1.3 基础知识
TS中的类型层次
下层类型的值可以赋值给上层类型的变量或常量
unknown类型的变/常量可以指向任何类型的值
不存在never类型的变量 (never是空集)
不同语言的Top Type与Bottom Type
类型讲解
any
1.any比较特殊,其实它既是top type又是bottom type
也就是说,any类型的变量/常量 可以与 任何类型的变量或者常量进行相互赋值
但any类型是不安全的,无语言服务的,所以应该尽量避免使用
2.any具有传染性,他会使它所触及到的地方变得不安全。
所以TS在3.0引入了类型安全的unknown作为Top Type
3.any会隐藏bug
4.any会隐藏代码设计细节:丢失了对数据类型的设计
所以,我们要尽量避免使用any
在tsconfig中开启严格模式
unknown
unknown一定要写注解,TS不会把任何值推导为unknown
unknown类型只能进行等于和不等于操作
只有类型收窄后,才能进行相应的运算或函数调用
如果无法预知类型,不要用any,用unknown,收窄类型后再使用
boolean
布尔类型只有两个元素:true/false
let,var变量会被拓宽成boolean类型
const常量就是对应着字面量类型
有一点值得注意的是,true和false的联合类型,会被反推回boolean类型
number
number包括整数,浮点数,+-Infinity(正负无穷),NaN(not a number)
bigint
number范围[-(2^53-1),2^53-1]
想要使用bigint需要将编译选项设置的大于es2020
bigint是新引入的类型,可以表示任意大小的整数
bigint字面量是在数字后面加小写的n
bigint支持加,减,乘,除,求余,幂
但是bigint 不能与number混合运算,需要显式转换
string
symbol
let、var声明的变量推导成symbol类型
unique symbol必须是const类型
const常量推导成unique symbol
也可以显式注解成unique symbol
unique symbol不是 一个类型而是,一组类型。比如unique symbol b1和unique symbol b1x是两个类型
Symbol的第一个参数是描述,不是符号名,也不是ID。
Symbol.for在内部维护了一个字典,如果之前没有创建过,对应的描述,就返回一个新的符号,如果创建过,就返回已创建的符号。
object
使用keyof 可以获取对象类型上的key
使用Object.key可以获取对象上的key
array
一般array创建时,如果不指定类型,TS会自动判断类型
数组有两种注解方式
- T[]
- Array接口泛型
tuple
元组是数组的子类型。元组就是各个索引位上的元素类型是确定的数组。
因为元组的创建方法和数组是一样的,所以元组必须是显式注解。
enum
枚举本身是一种映射
未显式赋值的枚举会自动从0开始自增赋值
枚举为整数存在双向映射
显式赋值字符串的枚举不存在反向映射
枚举的合并
枚举可以拆分成多个字段,还可以与同名的namespace合并
常量枚举
常量枚举不会在值空间创建变量
所以引用常量枚举的地方都被替换成对应的值
但是可以通过编译器选项preserveConstEnum来控制
null,undefined,void,never
JS中 Undefined = {undefiend},应该表示尚未定义,但是实际上表示已声明,未赋值
Null = {null} 表示已声明,值为null或值为空
TS中void类型:函数没有显式返回值
never类型,函数无法返回
在js中,void是一个一元操作符:
它执行后面的表达式,
然后无条件返回undefined
在ES1.3之前,undefined是无法直接访问的!
只能通过void(0)的这种形式得到,ES1.3将undefined添加到global object之后才可以访问到。
02.TypeScript进阶
类型操作
keyof
keyof(索引类型查询操作符)是在TS2.1中引入的。
它获取类型上所有已知、public的键对应的类型联合
索引可以是number,string,symbol,但是symbol还没有支持,将在TS4.4之后支持
也就是说 keyof 这个函数 只能查询 接口的key 或者类似于 class 的key
对于普通的 string,number这种类型没有必要使用keyof
在接口中存在键和值
键存在类型,可以是string,number,symbol(symbol将在ts4.4之后支持)
值存在类型,可以是所有类型
接口
接口与类型别名可以认为是同一概念的两种语法
接口与类型别名的区别
- 类型别名更为通用,其右侧可以包含类型表达式(类型联合、类型交叉、条件类型),但接口右侧只能是某种结构({...})
- 接口间继承(extends)时TS会检查二者关系。但类型联合时TS会尽最大的努力尝试,不会报错
- 同一作用域中多个同名接口声明会被合并,但多个类型别名会报错
类class
class是在es2015中引入的新特性
这里只讲如何使用interface描述一个class
其实使用interface来描述class 就是
-
描述类中的属性的类型
-
描述类中的函数
- 形参类型
- 返回值类型
03.TypeScript高级
类型操作
映射类型
映射类型是一种创建对象类型的方法