You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
letdemo1: Objectdemo1=[]demo1={}demo1=1demo1=null// Error: Type 'null' is not assignable to type 'Object'demo1=undefined// Error: Type 'undefined' is not assignable to type 'Object'letdemo2: {}demo2=[]demo2={}demo2=1demo2=null// Error: Type 'null' is not assignable to type '{}'demo2=undefined// Error: Type 'undefined' is not assignable to type '{}'
lettype1: any// 被任意类型赋值type1=1// 赋值给任意类型lettype2: number=type1lettype3: unknown// 被任意类型赋值type3=1// 赋值给任意类型lettype4: number=type3// Error: Type 'unknown' is not assignable to type 'number'
unknown 在不进行类型推断的时候,无法直接使用;any 则没有这个限制
letstr1: unknown='string';str1.slice(0,1)// Error: Object is of type 'unknown'.letstr2: any='string';str2.slice(0,1)// Success
添加类型推断后则可以正常使用:
letstr: unknown='string';// 1. 通过 as 类型断言(strasstring).slice(0,1)// 2. 通过 typeof 类型推断if(typeofstr==='string'){str.slice(0,1)}
never 也存在主动的使用场景,比如我们可以进行详细的类型检查,对穷举之后剩下的 else 条件分支中的变量设置类型为 never,这样一旦 value 发生了类型变化,而没有更新相应的类型判断的逻辑,则会产生报错提示
constcheckValueType=(value: string|number)=>{if(typeofvalue==='string'){// do something}elseif(typeofvalue==='number'){// do something}else{constcheck: never=value// do something}}
例如这里 value 发生类型变化而没有做对应处理,此时 else 里的 value 则会被收窄为 boolean,无法赋值给 never 类型,导致报错,这样可以确保处理逻辑总是穷举了 value 的类型:
constcheckValueType=(value: string|number|boolean)=>{if(typeofvalue==='string'){// do something}elseif(typeofvalue==='number'){// do something}else{constcheck: never=value// Error: Type 'boolean' is not assignable to type 'never'.// do something}}
interfacePerson{name: stringreadonlyage: number}constperson: Person={name: 'LiHua',age: 18}person.age=20// Cannot assign to 'age' because it is a read-only propertyconstlist: readonlynumber[]=[1,2]list.push(3)// Property 'push' does not exist on type 'readonly number[]'.list[0]=2// Index signature in type 'readonly number[]' only permits reading
类型别名
类型别名主要利用 type 关键字,来用于对一组特定类型进行封装,我们在 TypeScript 里的类型编程以及各种类型体操都离不开类型别名
interfaceTest1{name: string}typeTest2={name: string}constdata1: Test1={name: 'name1'}constdata2: Test2={name: 'name2'}interfacePropType{[key: string]: string}letprop: PropTypeprop=data1// Error: Type 'Test1' is not assignable to type 'PropType'. Index signature for type 'string' is missing in type 'Test1'prop=data2// success
extends 用于对泛型添加约束,使得泛型必须继承这些类型,例如这里要求泛型 T 必须要属于 string 或者 number:
typeTest<Textendsstring|number>=T[]typeTestExtends1=Test<string>// successtypeTestExtends2=Test<boolean>// Type 'boolean' does not satisfy the constraint 'string | number'.
本文旨在总结
TypeScript
的体系化知识,帮助你了解并熟悉TypeScript
的各项特性什么是 TypeScript
TypeScript
是JavaScript
的超集,通过添加静态类型定义与检查来对JavaScript
进行扩展,TypeScript
与JavaScript
的关系类似Less/Sass
与Css
为什么需要 TypeScript
JavaScript
是弱类型语言,很多错误会在运行时才被发现,而TypeScript
提供的静态类型检查可以帮助开发者避免大部分运行时的错误,并且能够大大增强代码的可维护性。相应的,付出的代价则是开发阶段需要书写相关的类型,成本方面有一定的提升Playground
TypeScript
官方提供了一个在线的TypeScript
开发环境 Playground,你可以很方便地在上面进行TypeScript
的相关练习,支持配置tsconfig
,静态类型检测以及TypeScript
代码编译执行等原始数据类型
在
TypeScript
中,对于JavaScript
中的原始数据类型都有对应的类型:e.g.
object
object
表示所有的非原始类型,包括数组、对象、函数等e.g.
Object 与 {}
在
JavaScript
里Object
是所有原型链的最上层,在TypeScript
里则表现为Object
可以表示所有的类型, 而{}
均表示所有非null
和undefined
的类型,null
和undefined
在strictNullChecks=false
时才允许被赋值给Object
和{}
其他类型
数组
数组定义有两种方式:
元组
数组合并了相同的类型,元组则合并不同的类型:
元组中的选项还可以是可选的
函数
函数定义方式可以是以下几种:
void
在
JavaScript
中,void
作为立即执行的函数表达式,用于获取undefined
:在
TypeScript
中则描述了一个函数没有显示返回值时的类型,例如下面这几种情况都可以用 void 来描述:any 与 unknown
区别
any
,any
也可以赋值给任意类型;任意类型都能赋值给unknown
,但是unknown
只能赋值给unknown/any
类型:添加类型推断后则可以正常使用:
never
表示不存在的类型,一般在抛出异常以及出现死循环的时候会出现:
never
也存在主动的使用场景,比如我们可以进行详细的类型检查,对穷举之后剩下的 else 条件分支中的变量设置类型为never
,这样一旦value
发生了类型变化,而没有更新相应的类型判断的逻辑,则会产生报错提示例如这里 value 发生类型变化而没有做对应处理,此时 else 里的
value
则会被收窄为boolean
,无法赋值给never
类型,导致报错,这样可以确保处理逻辑总是穷举了value
的类型:字面量类型
指定具体的值作为类型,一般与联合类型一起使用:
枚举
枚举使用
enum
关键字来声明:JavaScript
对象是单向映射,而对于TypeScript
中的枚举,字符串类型是单向映射,数字类型则是双向映射的,上面的枚举编译成JavaScript
会被转换成如下内容:对于数字类型的枚举,相当于执行了
obj[k] = v
和obj[v] = k
,以此来实现双向映射常量枚举
使用
const
定义,与普通枚举的区别主要在于不会生成上面的辅助函数TestEnum
,编译产物只有const val = 2
接口
接口
interface
是对行为的抽象,TypeScript
里常用来对对象进行描述可选
可选属性,通过
?
将该属性标记为可选readonly
只读属性,对于对象修饰对象的属性为只读;对于 数组/元组 只能将整个 数组/元组 标记为只读
类型别名
类型别名主要利用
type
关键字,来用于对一组特定类型进行封装,我们在TypeScript
里的类型编程以及各种类型体操都离不开类型别名Interface 与 type 的异同点
相同点:
不同点:
type
可以用来定义原始类型、联合/交叉类型、元组等,interface
则不行interface
声明的同名类型可以进行合并,而type
则不可以,会报标识符重复的错误interface
会有索引签名的问题,而type
没有因为只有当该类型的所有属性都已知并且可以对照该索引签名进行检查时,才允许将子集分配给该索引签名类型。而
interface
允许类型合并,所以它的最终类型是不确定的,并不一定是它定义时的类型;type
声明的类型时的索引签名是已知的联合类型与交叉类型
联合类型
表示一组可用的类型集合,只要属于其中之一就属于这个联合类型
交叉类型
表示一组类型的叠加,需要满足所有条件才可以属于这个交叉类型,一般用于接口的合并
如果新的类型不可能存在,则会被转换为
never
,例如这里的number & string
:对于对象类型的交叉类型,会按照同名属性进行交叉,例如下面的
common
需要即包含fieldA
也包含fieldB
:如何绕过类型检测
鸭子类型
鸭子类型放在
TypeScript
里来说就是我们可以在鸟上构建走路、游泳、叫等方法,创建一只像鸭子的鸟,来绕开对鸭子的类型检测e.g.
在这里我们构造了一个函数
func
接受参数为Param
,当我们直接调用func
传参时,相当于是赋值给变量param
,此时会严格按照参数校验进行,因此会报错;而如果我们使用一个变量存储,再将变量传递给
func
,此时则会应用鸭子类型的特性,因为param1
中 包含field1
,TypeScript
会认为param1
已经完全实现了Param
,可以认为param1
对应的类型是Param
的子类,这个时候则可以绕开对多余的field2
的检测类型断言
类型断言也可以绕过类型检测,上面的例子可以改成用类型断言来实现:
另外一种断言方式是非空断言,利用
!
关键词,可以从类型中排除undefined
和null
:泛型
泛型是一种抽象类型,只有在调用时才知道具体的类型。如果将类型类比为函数,那么泛型就相当于函数中的参数了
函数中定义泛型
类型操作符
在
TypeScript
中,可以通过类型操作符来对类型进行操作,基于已有的类型创建新的类型,主要包括以下几种:typeof
typeof
可以获取变量或者属性对应的类型,返回的是一个 TypeScript 类型:对于对象类型的变量,则会保留键名,返回推断得到的键值的类型:
keyof
keyof
用于获取类型中所有的键,返回一个联合类型:in
in
用于遍历类型,它是JavaScript
里已有的概念:extends
extends
用于对泛型添加约束,使得泛型必须继承这些类型,例如这里要求泛型T
必须要属于string
或者number
:extends
还可以在条件判断语句中使用:infer
infer
主要用于声明一个待推断的类型,只能结合extends
在条件判断语句中使用,我们以内置的工具类ReturnType
为例,它主要作用是返回一个函数返回值的类型,这里用infer
表示待推断的函数返回值类型:索引类型与映射类型
索引类型
这里声明了一个包含索引签名且键为
string
的类型:包含索引签名时,其他具体键的类型也需要符合索引签名声明的类型:
获取索引类型,通过
keyof
关键字,返回一个由索引组成的联合类型:访问索引类型,通过访问键的类型,来获取对应的索引签名的类型:
因此我们还可以通过键的类型来访问:
映射类型
与索引类型常常搭配使用的是映射类型,主要概念是根据键名映射得到键值类型,从旧的类型生成新的类型。我们利用
in
结合keyof
来对泛型的键进行遍历,即可得到一个映射类型,很多 TypeScript 内置的工具类的实现都离不开映射类型。以实现一个简单的
ToString
,能将接口中的所有类型映射为string
类型为例:工具类型
这里我们列举了一些
TypeScript
内置的常用工具链的具体实现:Partial
将所有属性变为可选,首先通过
in
配合keyof
遍历T
的所有属性赋值给P
,然后配合?
将属性变为可选,最后T[P]
以及undefined
作为返回类型:使用示例:
Partial
只能将最外层的属性变为可选,类似浅拷贝,如果要想把深层地将所有属都变成可选,可以手动实现一下:Required
将所有属性变为必选,与
Partial
实现的思路类似,只不过变成了通过-?
来去除可选符号:使用示例:
Readonly
将所有属性都变成只读,不可修改,与
Partial
实现的思路类似,利用readonly
关键字来标识:使用示例:
Record
以指定的类型生成对应类型的键值对,例如我们经常会使用
Record<string, unknown>
或者Record<string, any>
来对对象的类型进行声明,这里主要通过K extends string | number | symbol
来限制K
必须符合索引的类型:Exclude
移除属于指定类型的部分,通过判断如果
T
继承自U
,那么返回never
,则会移除T
中属于U
的类型:使用示例:
Extract
保留属于指定类型的部分,与
Exclude
逻辑相对应,在这里则指保留T
中属于U
的类型:使用示例:
NonNullable
去除类型中的
null
和undefined
:使用示例:
Pick
以选中的属性生成新的类型,类似
lodash.pick
,这里首先通过extends
配置keyof
获取到T
中的所有子类型并赋值给K
,当P
属于K
中的属性时,返回T
对应的类型T[P]
:使用示例:
Omit
排除选中的属性,以剩余的属性生成新的类型,与
Pick
作用刚好相反,类似lodash.omit
,这里首先通过Exclude<keyof T, K>
来去除掉T
中包含的属性K
,然后当P
属于该去除后的类型时,返回T
对应的类型T[P]
:使用示例:
Parameters
获得函数参数的类型,返回一个元组,这里首先通过扩展运算法,将泛型函数中的参数通过
infer
定义为P
,然后判断T
是否符合函数的类型定义,如果是则返回P
:使用示例:
ReturnType
获取函数返回值的类型,实现与
Parameters
类似,将定义的类型从函数参数调整为函数的返回值类型:tsconfig
tsconfig
是TypeScript
的项目配置文件,通过它你可以配置TypeScript
的各种类型检查以及编译选项,这里主要介绍一些常用的compilerOptions
选项:关于
The text was updated successfully, but these errors were encountered: