TypeScript
初始化第一个TypeScript项目
首先创建文件夹TS,在vscode中打开该文件夹,我习惯使用vs里面集成的终端,所以下一步是在vs中打开终端,初始化项目生成package.json文件
npm init -y
第二步安装ts依赖
npm i typescript -S #最新稳定版
上面是安装最新的稳定版,如果想尝试最新开发版,可以使用下面代码指令安装
npm i typescript@next -S #nightly build
接下来我们要创建tsconfig.json文件,是ts的配置文件
node_modules/.bin/tsc --init --locale zh-CN
在我们刚才安装的依赖包里面bin目录下有一个tsc文件,可以使用上面的命令帮助我们创建tsconfig.json文件
这个config文件包含所有ts可配置的选项,后面也有相应的注释,帮助我们更好的配置ts
把鼠标悬停在属性上面,vs会给我们展示该属性的解释和提示以及官方文档的链接,点击查看更详细的解释
如何选择ts版本
点击注脚的ts版本可以对ts版本进行设置
右边三个分别是使用vs自带的ts版本,使用工作区的版本,使用刚才安装的nodemodules里面的版本
当我们选择nodemodules中的版本时,在工作区会生成一个vs的settings.json文件,里面指定了当前ts的tsdk的版本来源,指向了我们安装的ts包中的lib文件夹内
TS与JS的关系
typescript是JavaScript的超集
typescript提供所有JavaScript的特性,并在其上层增加了typescript类型系统
这个类型系统被设计为 "可选的" .这就意味着:所有的合法JavaScript代码都是合法的typescript代码
TS的编译过程
TS的类型检查与生成JS是两个独立的过程,类型检查出错不影响生成的JavaScript代码
我们知道,C++这种编译型语言,当我们在编辑器或者IDE中编辑好代码,点击build或者构建的时候,我们的代码会送给g++,g++进行读取相应的头文件,编译失败会报错,编译成功会生成对应的可执行性文件给操作系统去执行.
那我们的TS文件在编译的时候,首先在编辑器中编辑,如果出现错误,会由TSServer给我们提示,点击编译会发给tsc,tsc会在相关的模块中查找使用到的模块进行输出.这里跟c++这类语言不同的是,ts在编译过程中如果出现报错,也会生成js文件.
类型系统
强类型的编程语言中都会有类型系统,大致分为一下两类
结构类型系统(Structural Type System)
通过类型的实际结构确定两个类型是否相等或兼容
(TypeScript,Haskell,OCaml,Go,...)
看一下示例代码
class Foo {
public hi(){
console.log("hi")
}
}
class Bar {
public hi(){
console.log("hello")
}
}
const a : Foo = new Foo()
const b : Bar = a
b.hi() //works!
我们看到,在ts的代码中,两个不同的类Foo和Bar,虽然类名不一样,但是整体结构一样,都有一个名为"hi"的方法,所以两个类可以互相赋值
名义类型系统(Nominal Type System)
通过类型的名称确定两个类型是否相等
(C,C++,Java,C#,...)
#include <iostream>
class Foo {
public:
void hi() { std :: cout << "Hi" << std :: endl;}
};
class Bar {
public:
void hi() { std :: << "Hello" << std::endl;}
}
int main (){
Foo foo;
Foo & fooRef = foo; //works!
//Error:type 'Bar' cannot
//bind to a value of unrelated type 'Foo'
Bar & bar = foo;
return 0;
}
但是在C++这种名义类型的语言中,虽然Foo和Bar结构相同,但是类名不同,不能完成赋值
类型注解
不同风语言的类型注解语法
// C++
int printf (const char*, ...)
# Objective-c
- (id)initWinthInt:(int)value;
//Julia
commission(sale::Int , rate::Float64)::Float64
#TypeScript
function log(message: string):void
前面两种语言的类型注解是放在类型的前面
而Juila的注解是放在后面,用两个冒号链接
TS的也是在后面,但是是用一个冒号链接
类型与集合的关系
空集(Empty Type)
never = {}
单元素集合(Unit Type)
Null = {null}
Undefined = {undefined}
Literal Type(字面量类型,'a',1,true)
有限集合
Boolean = {true,false}
无限集合(近似)
String = {'a','b', ... 'hello', ...}
Symbol = {Symbol(...), ...}
BigInt = {..., -1n,0n,1n,...}
Number = {-Infinity,-(2^53 - 1),...0,...+(2^53 - 1),Infinity} 和 NaN
全集(Universal Set)
类型联合与交叉
名字 | 联合类型(Union Types) 集合并集(Unio) | 交叉类型(Intersection Types) 集合交集(Intersection) | |
---|---|---|---|
图示 | |||
TS写法 数学写法 | A | B (A或B) A U B (A并B) | A & B (A与B) A ^ B (A交B) |
类型别名
在JS中我们可以使用let, const, var声明变量或常量
在TS中我们可以使用type为类型声明别名
// Value Space
let ID = 9527
const PI = 3.14
var myPI = PI
// Type Space
type ID = string
type User = {
id:ID;
name:string
}
//Error: Duplicate identifier 'User'
type User = {}
//块级作用域
{
//OK
type User = {
id:number;
name:string
}
}
上面我们可以看到TS的声明会用type,类型别名和let变量类似,也采用块级作用域.所以,同一个作用域内不能重名
当我们用{}括起来的块级作用域内就可以使用与外接想要同的别名,与let作用域相同
类型的拓宽与收窄
类型拓宽(Type Widening)
当你用字面量赋值给let,var变量时,TS不用字面量类型作为该变量的类型,而是从字面量类型拓宽到响应的更宽泛的类型,这个过程叫做类型拓宽
例如我们使用type定义给let或var变量时,不会仅限于字面量类型,number或者string类型会进行更宽泛的类型
类型收窄(Type Narrowing)
在某些情况下,TS可以更加确定变量的类型,此时它会将变量类型收窄
TS试图在类型确定性与灵活性之前取得平衡
TS提供一系列方法来帮助收窄类型,以提高类型的确定性:
null check,as const,instanceof,typeof
属性检查,Tagged Union ,用户类型守卫(User-defined Type Guard),代码流分析
用let和var声明变量时,TS认为变量未来会发生改变,所以将类型推断为相对宽泛的类型
用const声明变量时,TS知道常量不会改变,会将类型推断为最窄的字面量类型
值空间与类型空间
所谓的类型空间是编译期存在的各种类型,这个空间是编译器tsc创建的
值空间是js的V8引擎在运行的时候创建的,里面存在了各种类型的值
只包含类型声明的namespace不会产生JS代码,不会引入变量
instanceof操作符只作用于值空间
interface IPoint {
x:number;
y:number
}
const p :IPoint = {x:0,Y:0}
//
if(p instanceof IPoint){
// 这里if的判断会报错,因为IPoint在上面只有类型没有值
}
//如果使用class声明,就不会报错,因为class会同时赋予类型和值
class Point implements IPoint {
constructor(
public x :number,
public y :number
){}
}
if(p indetanceof IPoint){
}
如何判断符号是在哪个空间?
- 转译后消失的符号 -> 类型空间
- 作为类型注解.别名的符号 -> 类型空间
- 类型断言后的符号 -> 类型空间(target as/is HTMLElement)
- const let var后面的符号 -> 值空间
- class enum namespace 后的符号 -> 值空间+类型空间
另外,有一些操作符在两个空间都存在,但是含义完全不同
typeof
在值空间,typeof后面表达式对应的Javascript类型的字符串表示:
('string','number','bigint','boolean','symbol','undefined','object','function')
在类型空间,typeof返回标识符对应的TypeScript类型
class Person() const P = typeof Person console.log(p) // "function" const person = new Person() type T = typeof person //type T = person //转译成js代码为 "use strict" class person{ } const p = typeof person console.log(p) // "function" const person = new person()
-
this关键字
在值空间,this指向比较复杂
在类型空间,this可以作为类方法的返回值来实现链式调用
-
& | 运算符
在值空间表示"按位与" 和 "按位或"
在类型空间表示类型的交叉和联合
-
const
在值空间用来声明常量
在类型空间与as连用 即as const 常量断言 收窄类型
-
extends
在值空间用于定义子类
在类型空间用来进行类型约束或者接口继承
-
in
在值空间用于for循环和判断属性是否存在
在类型空间用于映射类型的key的声明
基础知识
TS的类型层次
划重点:下层类型的值可以赋给上层类型的变/常量
因为 unknown类型的变/常量可以只想任何类型的值
所以 不存在never类型的变量(never是空集)
不同语言的Top Type和Botton Type
语言 | Top Type | Bottom Type |
---|---|---|
TypeScript | unknow | never |
PHP | mixed | never |
Python | object | typing.NoReturn |
Kotlin | Any? | Nothing |
Scala | Any? | Nothing |
Java | java.lang.Object | |
C# | System.Object | |
Go | interface{} | |
Perl | Universal | |
C++ | std::any | |
C | void | |
Object Pascal | TObject |
any
1.any比较特殊,其实它即是Top Type 又是 Botton Type
也就是说:any类型的变量/常量<->任何其他类型的变量常量 可以互相赋值
但any类型是不安全的 无语言服务的 所以应该尽量避免使用
2.any具有传染性:它会使它所触及的地方变得不安全
所以TS在3.0引入了类型安全的unknown类型作为Top Type
3.any会隐藏bug
因为没有类型信息,即便错误使用,也没有任何报错
比如字符串birthDate没有getTime()方法,但不会报错
4.any会隐藏代码设计细节:丢失了对数据类型的设计
unknown
let a:unknown = 30 //typeof a ==unknown
//unknown 类型必须显示注解 TS不会吧任何值推导为unknown
let b1 = a > 123 // Error TS2571:Object is of type 'unknown'
let b2 = a < 123 //Error TS2571
let b3 = a ===123 // typeof b == boolean
// unknown类型只能进行等于和不等于比较
let c = a + 10 // Error TS2571
if(typeof a ==='number'){
let d = a + 10 //typeof d == number
// 只有类型收窄后才能进行相应的运算或函数调用
}
如果无法预知类型,不要使用any 用unknown 收窄类型之后再使用
布尔类型
布尔类型只有两个元素 true和false
let a = true //typeof a = boolean
var b = false // typeof b = boolean
const c = true //typeof c = true
//let var 变量会被拓展成 Boolean类型 const常量就是对应的字面量类型
let d: boolean = true //typeof d = boolean
let e: boolean = true //typeof e = boolean
//Error TS2322:Type 'false' is not assignable to type 'true'
let f :true = false
let g: true | false = true //let g :boolean
//有一点值得注意的是,true和false的联合类型,会被反推回boolean类型
number类型
number包括:整数,浮点数,+_Infinity(正负无穷),NaN
let a = 1234 //typeof a = number
var b = Infinity * 0.1 //typeof b = number
const PI = 3.14 //typeof PI = 3.14
const nan = NaN //typeof nan = number 虽然叫Not a number但是类型仍然是number
let c: number = 2.34 //typeof c = number
let d: 4.56 = 4.56 //typeof d = 4.56
//Error TS2322:Type '10' is not assignable to type '4.56'
let e: 4.56 = 10
bigint类型
bigint是新引入的类型,可以表示任意大小的整数,number范围[-(2^53 - 1),2^53 - 1)]
bigint字面量是在数字后面加小写n,bigint支持加减乘除,求余 幂
由于bigint类型比较新,我们需要在tsconfig文件中将"target"属性设置为大于ES2020
let a = 1234n // typeof a = bigint
const b 5678n // typeof b = 5678n
var c = a + b
const hugeString = BigInt("900789739482") //typeof hugeString = bigint
const hegeHex = BigInt("0*1fffffffffff") // typeof hugeHex = bigint
const rounded = 5n/2n // typeof rounded = bigint rounded = 2n
//Error : Operator '+' cannot be applied to types 'bigint' and '123'
let d = a + 123 // bigint 不能和number混合运算 需要显示转换
let e = a + BigInt(123)
let f = Number(a) + 123
字符串类型String
let a = "hello" // typeof a = string
let b = "world" // typeof b = string
const c = "!" // typeof c = "!"
type Dir = "north" | "south" | "east" | "west"
type Direction = Dir | Capitaliza<Dir> | Uppercase<Dir>
//联合
// type Direction = Dir | "North" | "South" | "East" | "West" |"NORTH" | "SOUTH" | "EAST" | "WEST"
function echoDir(dir: Direction) {consolo.log(dir)}
//dir必须是Direction类型
echoDir('north')
echoDir('NORTH')
echoDir('North')
echoDir('NoRth') // Error
type RT = XMLHttpRequestResponseType
//字符串字面量联合
// 常用区分数据类型
//type XMLHttpRequestResponseType = "" | "arraybuffer" | "blob" | "document" | "json" | "text"
符号类型(symbol)
import { Equal } form '@type-challenges/unils'
let a = Symbol('a') // typeof a = symbol
var a1 = Symbol('a') // typeof a1 = symbol
//let var 声明的变量推导成symbol类型
let a2 = unique symbol = a1 //Error
//unique symbol必须是const常量
const b1x = Symbol('b') //"unique symbol b1x"
// const 常量推导成unique symbol
//也可以显示注解成unique symbol
type X = Equal<typeof b1, typeof b1x> // false
//划重点 unique symbol不是一个类型 而是一组类型 比如unique symbol b1 和unique symbol b1x 是两个类型
对象类型(Object)
数组类型(array)
数组有两种注解方式 :1.T[] 2.Array T 接口泛型
let a = [1,2,3] //typeof a = number[]
var b = ['a','b'] // typeof b = string[]
let c = [1.'a'] // typeof c = (string | number)[]
const d = [2,'b'] // typeof d = (string | number)[]
//const 数组不会收窄 因为收窄就变成tuple类型了
a.push('red') // Error :Argument of type 'string' is not assignable
// to parameter of type 'number'
let e = [] // typeof e = any[] Ts无法判断空数组的类型 只能推断为any[]
e.push(1);
e.push('red')
function buildArray(){
let e = []; // typeof e = any []
e.push(1);
e.push('red') // let e:(string | number)[] 但是当离开作用域时 TS可以分析代码 推断出数组的类型
return e
}
const myarray = buildArray()
myarray.push(true) //Error: Argument of type 'boolean' is not
// assignable to parameter of type 'string|number'
myarray.push(2) //ok
myarray.push('bule') // ok
元组(Tuple)
元组是数组的子类型,元组各索引上的元素类型是确定的
知识点: 因为元组的创建方法和数组是一样的 所以元素必须显示注解
let a : [number] = [1]
let b : [string,string,number] = ['a','b',1]
b = ['c','d','e'] // Error :Type 'string' is not addignable to type 'number' 因为e的类型应该是数字
let c:[number,number?][] [ //支持可选操作符
[1,2],
[3.4,5.6],
[7]
]
type C = typeof c[0] // type C = [number,number?]
type StringTuple = [string,...string[]] //元组支持rest操作符
let d :StringTuple = ['a','b','c'];
type NBS = [number,boolean,...string[]]
let list: NBS = [1,false,'a','b','c']
[...string[]] 等价于string[],但[string,...string[]]不等价于string[],前者至少包含一个元素
枚举(Enum)
常量枚举
常量枚举不会在值空间创建变量
所有引用常量枚举的地方都被替换为对应的值
(但是可以通过perserveConstEnum编译器选项来控制)
{
"compilerOptions" :{
"preserveConstEnum" : true
}
}
const enum CJK {
Chinese,
Japanese = 'JP',
Koarean = 1
}
console.log(CJK.Chinese)
console.log(CJK.Japanese)
console.log(CJK[0]) // Error
console.log(CJK) // Error 常量枚举没有双向映射
const enum Colors { Red,Green,Blue}
const enum Colors {
Orange = 3,Yellow,Purple
//常量枚举仍然支持多分段 但是由于常量枚举不生成变量 所以不支持与namespace的合并
}
console.log(Colors.Blur)
//js代码
"use strict"
console.log(0 /* Chinese */)
console.log("JP" /* Japanese */) //TS会输出常量对应的Key 方便调试
console.log(CJK[0]) // Error
console.log(CJK) //Error
console.log(2 /* Blue */)
null undefined void never
JS中 Undefined = {undefined} 应该表示尚未定义 但是 实际上已声明 未赋值 Null = {null} 表示已声明 值为null或值为空
TS中void类型:函数没有显式返回值 never类型 函数无法返回