TS基础类型
- 字符串使用string定义
let a: string = '123'
- 数字类型使用number定义
1 | let notANumber: number = NaN;//NaN |
- 布尔类型boolean
注意:使用构造函数Boolean创造的对象不是布尔值
1 | let createdBoolean: boolean = new Boolean(1) |
- 空值void
void 类型的用法,主要是用在我们不希望调用者关心函数返回值的情况下,比如通常的异步回调函数
void也可以定义undefined 和 null类型
1 | let u: void = undefined |
null和undefined类型
1 | let u: undefined = undefined;//定义undefined |
void 和 undefined 和 null 最大的区别
与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 string 类型的变量
1 | //这样写会报错 void类型不可以分给其他类型 |
如果你配置了tsconfig.json开启了严格模式
1 | { |
那么,null不能赋予void类型, undefined可以赋予void类型
任意类型any
nodejs 环境执行ts
npm i @types/node –save-dev (node环境支持的依赖必装)
npm i ts-node -g
没有强制限定哪种类型,随时切换类型都可以 我们可以对 any 进行任何操作,不需要检查类型
1
2
3let anys:any = 123
anys = '123'
anys = true声明变量的时候没有指定任意类型默认为any
1
2
3let anys;
anys = '123'
anys = true弊端如果使用any 就失去了TS类型检测的作用
TypeScript 3.0中引入的 unknown 类型也被认为是 top type ,但它更安全。与 any 一样,所有类型都可以分配给unknown
unknow unknow类型比any更加严格当你要使用any 的时候可以尝试使用unknow
1 | //unknown 可以定义任何类型的值 |
1 | // 如果是any类型在对象没有这个属性的时候还在获取是不会报错的 |
接口和对象类型
在typescript中,我们定义对象的方式要用关键字
interface
(接口),我的理解是使用interface来定义一种约束,让数据的结构满足约束的格式。
1 | //这样写是会报错的 因为我们在person定义了a,b但是对象里面缺少b属性 |
1 | //重名interface 可以合并 |
可选属性使用?操作符
1 | //可选属性的含义是该属性可以不存在 |
任意属性
需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
1 | //在这个例子当中我们看到接口中并没有定义C但是并没有报错 |
只读属性
readonly 只读属性是不允许被赋值的只能读取
1 | //这样写是会报错的 |
添加函数
1 | interface Person { |
数组类型
1、类型[]
类型加中括号
1 | //类型加中括号 |
数组泛型
规则: Array<类型>
1 | let arr: Array<number> = [1, 2, 3]; |
用接口表示数组
一般用来表示类数组
1 | interface NumberArray { |
多维数组
1 | let data:number[][] = [[1,2], [3,4]]; |
arguments类数组
1 | function Arr(...args:any): void { |
arr在数组中的应用
一个常见的例子数组中可以存在任意类型
1 | let list: any[] = ['test', 1, [],{a:1}] |
函数扩展
函数的类型
1 | //注意,参数不能多传,也不能少传 必须按照约定的类型来 |
函数的可选参数?
1 | //通过?表示该参数为可选参数 |
函数参数的默认值
1 | const fn = (name: string = "我是默认值"): string => { |
接口定义函数
1 | //定义参数 num 和 num2 :后面定义返回值的类型 |
定义剩余参数
1 | const fn = (array:number[],...items:any[]):any[] => { |
函数重载
重载是方法名字相同,而参数不同,返回类型可以相同也可以不同
如果参数类型不同,则参数类型应设置为 any
参数数量不同你可以将不同的参数设置为可选
1 | function fn(params: number): void |
类型断言|联合类型|交叉类型
联合类型
1 | //例如我们的手机号通常是13XXXXXXX 为数字类型 这时候产品说需要支持座机 |
函数使用联合类型
1 | const fn = (something:number | boolean):boolean => { |
交叉类型
多种类型的集合,联合对象将具有所联合类型的所有成员
1 | interface People { |
类型断言
语法:值 as 类型(value as string) 或 <类型>值(
value)
1 | interface A { |
需要注意的是,类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误,反而滥用类型断言可能会导致运行时错误:
使用any临时断言
1 | window.abc = 123 |
as const
是对字面值的断言,与const直接定义常量是有区别的
如果是普通类型跟直接const 声明是一样的
1 | const names = 'test' |
类型断言是不具影响力的
在下面的例子中,将 something 断言为 boolean 虽然可以通过编译,但是并没有什么用 并不会影响结果, 因为编译过程中会删除类型断言
1 | function toBoolean(something: any): boolean { |
内置对象
JS中有很多的内置对象,他们可以直接在TS中当作定义好的类型。
ECMAScript内置对象
Boolean、Number、String、RegExp、Date、Error
1 | let b: Boolean = new Boolean(1) |
DOM和BOM的内置对象
Document、HTMLElement、Event、NodeList等
1 | let body: HTMLElement = document.body; |
定义Promise
如果我们不指定返回的类型TS是推断不出来返回的是什么类型
1 | function PromiseFunc() { |
这样定义,TS是不知道PromiseFunc返回的类型是什么Promise<unknown>
所以,我们需要指定返回的类型
1 | function promise():Promise<number>{ |
Class类
定义类
在TypeScript是不允许直接在constructor 定义变量的 需要在constructor上面先声明
如果了定义了变量不用 也会报错 通常是给个默认值 或者 进行赋值
1 | class Person { |
类的修饰符
- public: 可以让你定义的变量,内部访问,也可以外部访问,不写默认就是public
- private: 只能内部访问,子类不能访问到,外部也不能
- protected:只能内部访问,子类可以访问到,外部不能访问
static静态属性和静态方法
static 定义的属性 不可以通过this 去访问 只能通过类名去调用
1 | class Person { |
static 静态函数 同样也是不能通过this 去调用 也是通过类名去调用
需注意: 如果两个函数都是static 静态的是可以通过this互相调用
interface 定义 类
ts interface 定义类 使用关键字 implements 后面跟interface的名字多个用逗号隔开
继承还是用extends
1 | interface PersonClass { |
抽象类
应用场景如果你写的类实例化之后毫无用处此时我可以把他定义为抽象类
或者你也可以把他作为一个基类-> 通过继承一个派生类去实现基类的一些方法
下面这段代码会报错抽象类无法被实例化
1 | abstract class A { |
例子2: 我们在A类定义了 getName 抽象方法但未实现
我们B类实现了A定义的抽象方法 如不实现就不报错 我们定义的抽象方法必须在派生类实现
1 | abstract class A { |
元组类型
如果需要一个固定大小的不同类型的集合,我们需要使用元组
元组就是数组的变种
元组(Tuple)是固定数量的不同类型的元素的组合
元组与集合的不同之处在于,元组中的元素类型可以是不同的,而且数量固定。元组的好处在于可以把多个元素作为一个单元传递。如果一个方法需要返回多个值,可以把这多个值作为元组返回,而不需要创建额外的类来表示。
1 | let arr:[number,string] = [1,'string'] |
当赋值或访问一个已知索引时,会得到正确的类型
越界元素
1 | let arr:[number,string] = [1,'string'] |
对于越界的元素他的类型被限制为 联合类型(就是你在元组中定义的类型)
应用场景:例如定义Excel返回的数据
1 | let excel: [string, string, number, string][] = [ |
枚举类型
在javaScript中是没有枚举的概念的TS帮我们定义了枚举这个类型,通过enum关键字定义枚举
数字枚举
例如 红绿蓝 Red = 0 Green = 1 Blue= 2 分别代表红色0 绿色为1 蓝色为2
1 | enum Types{ |
这样写就可以实现应为ts定义的枚举中的每一个组员默认都是从0开始的所以也就是
1 | enum Types { |
增长枚举
1 | enum Types{ |
如上,我们定义了一个数字枚举, Red使用初始化为 1。 其余的成员会从 1开始自动增长。 换句话说, Type.Red的值为 1, Green为 2, Blue为 3。
字符串枚举
字符串枚举的概念很简单。 在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。
1 | enum Types{ |
由于字符串枚举没有自增长的行为,字符串枚举可以很好的序列化。
异构枚举
枚举可以混合字符串和数字成员
1 | enum Types{ |
接口枚举
1 | enum Types { |
const枚举
let 和 var 都是不允许的声明只能使用const
大多数情况下,枚举是十分有效的方案。 然而在某些情况下需求很严格。 为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,我们可以使用 const枚举。 常量枚举通过在枚举上使用 const修饰符来定义
const 声明的枚举会被编译成常量
普通声明的枚举编译完后是个对象
1 | const enum Types{ |
const编译之后:console.log(12 /* Blue */);
反向映射
它包含了正向映射( name -> value)和反向映射( value -> name)
要注意的是 不会为字符串枚举成员生成反向映射
1 | enum Enum { |
类型推论|类型别名
什么是类型推论
let str = "ceshi"
- 我声明了一个变量但是没有定义类型
TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论,不能够在赋值给别的类型
- 如果你声明变量没有定义类型也没有赋值这时候TS会推断成any类型可以进行任何操作
类型别名
type关键字(可以给一个类型定义一个名字)==>多用于符合类型
- 定义类型别名
1
2type str = string;
let s: str = 'ces'; - 定义函数别名
1
2type str = () => string
let s: str = () => 'aaa'; - 定义联合类型别名
1
2
3type str = string | number;
let s: str = 123
str = '555' - 定义值的别名
1
2type value = boolean | 0 | '213'
let s: value = true;
never类型
TypeScript 将使用 never 类型来表示不应该存在的状态(很抽象是不是)
1 | // 返回never的函数必须存在无法达到的终点 |
never和void差异
1 | //void类型只是没有返回值 但本身不会出错 |
never 类型的一个应用场景
1 | interface A { |
比如新来了一个同事他新增了一个C接口,我们必须手动找到所有 switch 代码并处理,否则将有可能引入 BUG 。
而且这将是一个“隐蔽型”的BUG,如果回归面不够广,很难发现此类BUG。
由于任何类型都不能赋值给 never 类型的变量,所以当存在进入 default 分支的可能性时,TS的类型检查会及时帮我们发现这个问题
symbol类型
symbol类型的值是通过Symbol构造函数创建的
可以传递参做为唯一标识 只支持 string 和 number类型的参数
1 | let sym1 = Symbol(); |
Symbol的值是唯一的
1 | const s1 = Symbol() |
用作对象属性的键
1 | let sym = Symbol(); |
使用symbol定义的属性,是不能通过如下方式遍历拿到的
1 | const symbol1 = Symbol('666') |
如何拿到
1 | // 1 拿到具体的symbol 属性,对象中有几个就会拿到几个 |
Symbol.iterator迭代器和生成器for of
支持遍历大部分类型迭代器 arr nodeList argumetns set map 等
1 | var arr = [1,2,3,4]; |
测试用例
1 | interface Item { |
symbols列表
Symbol.hasInstance
方法,会被instanceof运算符调用。构造器对象用来识别一个对象是否是其实例。Symbol.isConcatSpreadable
布尔值,表示当在一个对象上调用Array.prototype.concat时,这个对象的数组元素是否可展开。Symbol.iterator
方法,被for-of语句调用。返回对象的默认迭代器。Symbol.match
方法,被String.prototype.match调用。正则表达式用来匹配字符串。Symbol.replace
方法,被String.prototype.replace调用。正则表达式用来替换字符串中匹配的子串。Symbol.search
方法,被String.prototype.search调用。正则表达式返回被匹配部分在字符串中的索引。Symbol.species
函数值,为一个构造函数。用来创建派生对象。Symbol.split
方法,被String.prototype.split调用。正则表达式来用分割字符串。Symbol.toPrimitive
方法,被ToPrimitive抽象操作调用。把对象转换为相应的原始值。Symbol.toStringTag
方法,被内置方法Object.prototype.toString调用。返回创建对象时默认的字符串描述。Symbol.unscopables
对象,它自己拥有的属性会被with作用域排除在外。
泛型
函数泛型
1 | function num (a:number,b:number) : Array<number> { |
泛型优化
语法为函数名字后面跟一个<参数名> 参数名可以随便写 例如我这儿写了T
当我们使用这个函数的时候把参数的类型传进去就可以了 (也就是动态类型)
1 | function Add<T>(a: T, b: T): Array<T> { |
我们也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以
1 | function Sub<T,U>(a:T,b:U):Array<T|U> { |
定义泛型接口
声明接口的时候,在名字后面加一个<参数>
使用的时候传递类型
1 | interface MyInter<T> { |
对象字面量泛型
1 | let foo: { <T>(arg: T): T } |
泛型约束
我们期望在一个泛型的变量上面,获取其length参数,但是,有的数据类型是没有length属性的
1 | function getLegnth<T>(arg:T) { |
这时候我们就可以使用泛型约束
于是,我们就得对使用的泛型约束,我们约束其为具有length属性的类型,这里我们会用到interface,代码如下
1 | interface Len { |
使用keyof约束对象
其中使用了TS泛型和泛型约束。首先定义了T类型并使用extends关键字继承object类型的子类型,然后使用keyof操作符获取T类型的所有键,它的返回 类型是联合 类型,最后利用extends关键字约束 K类型必须为keyof T联合类型的子类型
1 | function prop<T, K extends keyof T>(obj: T, key: K) { |
泛型类
声明方法跟函数类似名称后面定义<类型>
使用的时候确定类型new Sub<number>()
1 | class Sub<T>{ |
tsconfig.json配置文件
1 | "compilerOptions": { |
常用:
- include: 指定编译文件默认是编译当前目录下所有的ts文件
- exclude: 指定排除的文件
- target: 指定编译js的版本
- allowJS: 是否允许编译js文件
- removeComments: 是否在编译过程中删除文件中的注释
- rootDir: 编译文件的目录
- outDir: 输出的目录
- sourceMap: 代码源文件
- strict: 严格模式
- module: 默认commonJS,可选es模式、amd、umd等
namescpace命名空间
我们在工作中无法避免全局变量造成的污染,TypeScript提供了namespace 避免这个问题出现
- 内部模块,主要用于组织代码,避免命名冲突。
- 命名空间内的类默认私有
- 通过 export 暴露
- 通过 namespace 关键字定义
TypeScript与ECMAScript 2015一样,任何包含顶级import或者export的文件都被当成一个模块。相反地,如果一个文件不带有顶级的import或者export声明,那么它的内容被视为全局可见的(因此对模块也是可见的)
命名空间中通过export将想要暴露的部分导出
如果不用export 导出是无法读取其值的
1 | namespace a2 { |
嵌套命名空间
1 | namespace a { |
抽离命名空间
1 | //a.ts |
简化命名空间
1 | namespace A { |
合并命名空间
重名的命名空间会合并
三斜线指令
三斜线指令是包含单个XML标签的单行注释。 注释的内容会做为编译器指令使用。
三斜线指令仅可放在包含它的文件的最顶端。 一个三斜线指令的前面只能出现单行或多行注释,这包括其它的三斜线指令。 如果它们出现在一个语句或声明之后,那么它们会被当做普通的单行注释,并且不具有特殊的涵义。/// <reference path="..." />
指令是三斜线指令中最常见的一种。 它用于声明文件间的 依赖。 =>三斜线引用告诉编译器在编译过程中要引入的额外的文件
你也可以把它理解能import,它可以告诉编译器在编译过程中要引入的额外的文件
例如:
1 | //a.ts |
声明文件引入
例如,把 /// <reference types="node" />
引入到声明文件,表明这个文件使用了 @types/node/index.d.ts
里面声明的名字; 并且,这个包需要在编译阶段与声明文件一起被包含进来。
仅当在你需要写一个d.ts文件时才使用这个指令
1 | ///<reference types="node" /> |
注意事项:
如果你在配置文件 配置了noResolve
或者自身调用自身文件会报错
声明文件d.ts
声明文件declare
当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能
1 | declare var //声明全局变量 |
- 例如我们有一个express 和 axios
发现express 报错了
让我们去下载他的声明文件npm install @types/node -D
那为什么axios 没有报错
我们可以去node_modules
下面去找axios 的package json
发现axios已经指定了声明文件 所以没有报错可以直接用
通过语法declare 暴露我们声明的axios 对象declare const axios: AxiosStatic;
如果有一些第三方包确实没有声明文件我们可以自己去定义
名称.d.ts 创建一个文件去声明
Mixins混入
TS混入Mixins,可以将它看成合并
对象混入
可以使用es6的Object.assign合并多个对象
1 | interface Name { |
此时 people 会被推断成一个交叉类型 Name & Age & sex;
类的混入
首先声明两个mixins类 (严格模式要关闭不然编译不过)
1 | class A { |
下面创建一个类,结合了这两个mixins
首先应该注意到的是,没使用extends而是使用implements
。 把类当成了接口
我们可以这么做来达到目的,为将要mixin进来的属性方法创建出占位属性。 这告诉编译器这些成员在运行时是可用的。 这样就能使用mixin带来的便利,虽说需要提前定义一些占位属性
1 | class C implements A,B{ |
最后,创建这个帮助函数,帮我们做混入操作。 它会遍历mixins上的所有属性,并复制到目标上去,把之前的占位属性替换成真正的实现代码
**Object.getOwnPropertyNames()
可以获取对象自身的属性,除去他继承来的属性,对它所有的属性遍历,它是一个数组,遍历一下它所有的属性名**
1 | Mixins(C, [A, B]) |
装饰器Decorator
Decorator装饰器是一项实验性特性,在未来的版本中可能会发生改变
它们不仅增加了代码的可读性,清晰地表达了意图,而且提供一种方便的手段,增加或修改类的功能
若要启用实验性的装饰器特性,你必须在命令行或tsconfig.json里启用编译器选项
1 | { |
装饰器
装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,访问符,属性或参数上
1 | //定义一个类装饰器函数 他会把Class A的构造函数传入你的watcher函数当做第一个参数 |
装饰器工厂函数
其实也就是一个高阶函数 外层的函数接受值 里层的函数最终接受类的构造函数
1 | const watcher = (name: string): ClassDecorator => { |
装饰器组合
就是可以使用多个装饰器
方法装饰器
返回三个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
- 成员的属性描述符。
1
2
3
4
5
6
7
8
9
10
11const met:MethodDecorator = (...args) => {
console.log(args);
}
class A {
constructor() {}
getName ():string {
return '小满'
}
}
const a = new A();
属性装饰器
返回两个参数
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 属性的名字
1
2
3
4
5
6
7
8
9
10const met:PropertyDecorator = (...args) => {
console.log(args);
}
class A {
name:string
constructor() {
}
}
const a = new A();参数装饰器
返回三个参数 - 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
- 参数在函数参数列表中的索引
1
2
3
4
5
6
7
8
9
10const met:ParameterDecorator = (...args) => {
console.log(args);
}
class A {
constructor() {
}
setParasm (string = '213') { name:
}
}
const a = new A();
Rollup构建TS项目 & webpack构建TS项目
实战TS编写发布订阅模式
发布订阅模式例如
addEventListener
,Vue evnetBus
都属于发布订阅模式
首先 需要定义三个角色 发布者 订阅者 调度者
- on订阅/监听
- emit 发布/注册
- once 只执行一次
- off解除绑定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46interface EventFace {
on: (name: string, callback: Function) => void,
emit: (name: string, ...args: Array<any>) => void,
off: (name: string, fn: Function) => void,
once: (name: string, fn: Function) => void
}
interface List {
[key: string]: Array<Function>
}
class Dispatch implements EventFace {
list: List
constructor() {
this.list = {};
}
on(name: string, callback: Function): void {
const callbackList: Array<Function> = this.list[name] || [];
callbackList.push(callback);
this.list[name] = callbackList;
},
emit(name: string, fn: Function): void {
let eventName: Array<Function> = this.list[name];
if (eventName) {
eventName.forEach(eventFn => {
eventFn.apply(this, args);
})
} else {
console.error('该事件未监听');
}
},
off(name: string, fn: Function): void {
let eventName: Array<Function> = this.list[name];
if (eventName && fn) {
let index: number = eventName.findIndex(fns => fns === fn);
eventName.splice(index, 1);
} else {
console.error('该事件未监听');
}
},
once(name: string, fn: Function): void {
let dector = (...args) => {
fn.apply(this, args);
this.off(name, dector);
}
this.on(name, dector);
}
}
TS进阶用法Proxy & Reflect
Proxy
Proxy对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
target
要使用Proxy包装的目标对象(可以是任何类型的对象,包括元素数组,函数,甚至另一个代理)
handler
一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理p的行为
- handler.get()本次使用的get => 属性读取操作的捕捉器
- handler.set()本次使用的set => 属性设置操作的捕捉器
Reflect
与大多数全局对象不同Reflect并非一个构造函数,所以不能通过new 运算符对其进行调用,或者将Reflect对象作为一个函数来调用。Reflect的所有属性和方法都是静态的
Reflect.get(target, name, receiver)
Reflect.get方法查找并返回target对象的name属性,如果没有该属性返回undefined
Reflect.set(target, name, value, receiver)
Reflect.set方法设置target对象的name属性等于value.
1 | type Person = { |
使用泛型+keyof优化
1 | const logAccess = <T>(object: T, key: keyof T): T => { |
TS进阶用法Partial & Pick
Partial
TS内置高级类型Partial Pick
看一下源码
1 | /** |
- keyof:将一个接口对象的全部属性提取出来变成联合类型
- in: 我们可以理解成for in,遍历keyof T就是遍历联合类型的每一项
- ?:将一个属性变成可选属性
- T[P]: 索引访问操作符,与JS中访问属性值的操作类似
使用前
1 | type Person = { |
转换后全部转为了可选
1 | type p = { |
Pick
从类型定义T的属性中,选取指定一组属性,返回一个新的类型定义。
1 | /** |
1 | type Person = { |
TS进阶用法Record & Readonly
Readonly
和Partial相似,只是将?操作符替换成readonly
1 | type Readonly<T> = { |
- readonly:就是将属性变成只读
Record
1 | type Record<K extends keyof any, T> = { |
- keyof any 返回 string number symbol 的联合类型
- extends: 约束我们的类型
- T直接返回类型
TS进阶用法infer
infer 是TypeScript 新增到的关键字 充当占位符
条件类型推断的例子
定义一个类型 如果是数组类型 就返回 数组元素的类型 否则 就传入什么类型 就返回什么类型
1 | type Infer<T> = T extends Array<any> ? T[number] : T; |
使用infer修改
1 | type Infer<T> = T extends Array<infer U> ? U : T; |
配合tuple转化union联合类型
1 | type TupleToUni<T> = T extends Array<infer E> ? E : never; |