TypeScript 基础
- 一 数据类型
- 1 基础数据类型
- 2 任意类型
- 3 interface 接口
- 4 数组类型
- 5 函数类型
- 6 类型断言|联合类型|交叉类型
- 7 内置对象
- 8 class 类
- 9 元组
- 10 枚举 enum
- 11 类型推论|类型别名
- 12 never
- 13 Symbol
- 14 泛型 ***
- 15 tsconfig.json 文件
- 16 namespace 命名空间
- 17 三斜线指令
- 18 声明文件 d.ts
- 19 Mixins 混入
- 20 装饰器Decorator
- 21 webPack 构建 TS 项目
- 22 实战:TS 编写发布订阅模式
- 23 TS 进阶用法:proxy & Reflect
- 24 TS 进阶用法:Partial & Pick
一 数据类型
基础类型:Boolean、Number、String、null
、undefined
以及 ES6 的 Symbol 和 ES10 的 BigInt。
1 基础数据类型
1.1 string
字符串类型是使用 string
定义的。
//普通声明
let a: string = '123'
//也可以使用es6的字符串模板
let str: string = `dddd${a}`
1.2 number
使用 number
来定义数字类型,支持十六进制、十进制、八进制、二进制。
let notANumber: number = NaN; //Nan
let num: number = 123; //普通数字
let infinityNumber: number = Infinity;//无穷大
let decimal: number = 6; //十进制
let hex: number = 0xf00d; //十六进制
let binary: number = 0b1010; //二进制
let octal: number = 0o744; //八进制
1.3 boolean
// 这样会报错 应为事实上 new Boolean() 返回的是一个 Boolean 对象
let createdBoolean: boolean = new Boolean(1)
//
let createdBoolean: Boolean = new Boolean(1)
let booleand: boolean = true //可以直接使用布尔值
let booleand2: boolean = Boolean(1) //也可以通过函数返回布尔值
1.4 object
const user: { _id: number, name: string } = { id: 1, name: 'Li'};
// ? 表示可选属性
const user: { _id: number, name?: string } = { id: 1, name: 'Li'};
// [propName: string]: any 任意可选属性
const user: { _id: number, name: string, [propName: string]: any }
= { id: 1, name: 'Li', age: 18, gender: '男'};
//特殊的 可以赋值一个函数,也不会报错
let a: object;
a:{};
a = function () {};
1.5 void
JavaScript 没有空值的概念。
TypeScript 中可以用 void 表示没有任何返回值的函数
。
function voidFn():void {
// ...
}
void 类型的用法: 主要是用在不希望调用者关心函数返回值的情况下,比如通常的异步回调函数。
void 可以定义 undefined 和 null
let u: void = undefined;
let n: void = null;
1.6 unll 与 undefined
let u: undefined = undefined; // 定义 undefined
let n: null = null; // 定义 null
void 与 (undefined、null) 的区别:
undefined 和 null 是所有类型的子类
,也就是可以将 undefined 和 null 类型的变量赋值给其他类型的变量。
//这样是没问题的
let test: null = null
let s: string = "1"
s = test // 可以
//或者这样的
let test: undefined = undefined
let s: string = "1"
s = test // 可以
但 void 类型的变量是不能赋值给其他类型的。
// void 类型的变量不能赋值给其他类型,下面这样写会报错
let test: void = undefined;
let s: string = '1';
s = test; // 报错
2 任意类型
any 类型 和 unknown 顶级类型。
2.1 any
any 任意的意思,我们定义为 any 类型的变量,是不需要类型检查的,可以任意切换。
let anys:any = 123
anys = '123'
anys = true
// 声明变量的时候没有指定任意类型,默认为 any
let anys;
anys = '123'
anys = true
**弊端:**如果使用 any 就失去了 TS类型检测的作用
2.2 unknown
unknown 未知的意思。TypeScript 3.0中引入的 unknown 类型也被认为是 top type(顶级类型),但它更安全。与 any 一样,所有类型都可以分配给 unknown
。
unknow类型比any更加严格当你要使用any 的时候可以尝试使用unknow
//unknown 可以定义任何类型的值
let value: unknown;
value = true; // OK
value = 42; // OK
value = "Hello World"; // OK
value = []; // OK
value = {}; // OK
value = null; // OK
value = undefined; // OK
value = Symbol("type"); // OK
//这样写会报错unknow类型不能作为子类型只能作为父类型 any可以作为父类型和子类型
//unknown类型不能赋值给其他类型
let names:unknown = '123'
let names2:string = names
//这样就没问题 any类型是可以的
let names:any = '123'
let names2:string = names
//unknown可赋值对象只有unknown 和 any
let bbb:unknown = '123'
let aaa:any= '456'
aaa = bbb
区别2
// 如果是 any 类型在对象没有这个属性的时候还在获取是不会报错的
let obj:any = {b:1}
obj.a
// 如果是 unknown 是不能调用属性和方法
let obj:unknown = {b:1,ccc:():number=>213}
obj.b
obj.ccc()
2.3 区别
1 unknown 相比 any 更严格安全一些。
unknown 只能作为父类,也就是可以将任意类型是变量赋值给 unknown 类型的变量,但是不能将 unknown 类型的变量赋值给其他任意类型。
any 既可以作父类又可以作子类,即可以将 any 类型的变量赋值给其他任意类型,也可以将任意类型的变量赋值给 any类型。
2 对于属性和方法
unknown 类型是不能调用属性和方法的。
any 类型是可以的,而且在对象上获取一个不存在的属性也是不会报错的。
3 interface 接口
3.1 interface 是什么
在 TypeScript 中,定义对象的方式要用关键字 interface
(接口)。
可以理解为使用 interface 来定义一种约束,让数据的结构满足约束的格式。
也就是说可以通过 interface 定义一种特殊的多种结构的数据类型。
// 使用 interface 定义一个 Person 的类型,其中有两个为 string 的属性
interface Person {
b:string,
a:string
}
// 这里使用上面定义的 Person 接口约束
// 目前这样写是会报错的,因为使用接口约束的时候,属性的数量必须与接口定义的属性保持一致。不能多不能少
const person:Person = {
a:"213"
// 属性的数量要与接口定义的属性保持一致
// 但可以使用可选属性来解决这个问题 ?.
}
3.2 重名 interface 会进行合并
interface A { name:string }
interface A { age:number }
let x:A = { name:'xx', age:20 }
3.3 interface 可以使用 extends 进行继承
// 定义一个 A 接口
interface A {
name:string
}
// B 接口继承 A 中定义的属性
interface B extends A{
age:number
}
// 使用 B 接口
let test:B = {
age:18,
name:"string"
}
3.4 可选属性 使用?操作符
//可选属性的含义是该属性可以不存在,所以说这样写也是没问题的
interface Person {
a:string,
b?:string
}
const person:Person = {
a:"213"
}
3.5 任意属性 [propName: string]
需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:
// 使用 [propName: string]: any 定义任意属性
interface Person {
b?:string,
a:string,
[propName: string]: any;
}
//在这个例子当中我们看到接口中并没有定义C但是并没有报错
//应为我们定义了[propName: string]: any;
//允许添加新的任意属性
const person:Person = {
a:"213",
c:"123"
}
3.6 只读属性 readonly
readonly 只读属性是不允许被赋值的只能读取
//这样写是会报错的
//应为a是只读的不允许重新赋值
interface Person {
b?: string,
readonly a: string,
[propName: string]: any;
}
const person: Person = {
a: "213",
c: "123"
}
person.a = 123
3.7 添加函数
interface Person {
b?: string,
readonly a: string,
[propName: string]: any,
cb:()=>void
}
const person: Person = {
a: "213",
c: "123",
cb:()=>{
console.log(123)
}
}
3.8 总结:
1 使用 interface 定一个特殊的可约束的数据类型结构
2 在使用接口约束的时候,变量的属性与接口约束的属性保持一致,不能多也不能少。
3 重名的 interface 会进行合并
4 interface 可以使用 extends 进行继承
5 可以使用 ? 操作符定义可选属性,解决第二点的问题
6 [propName: string]: any 定义任意属性
7 readonly 只读属性,不允许被赋值,只能读取
8 ()=> void 同时是可以添加函数的。
4 数组类型
4.1 基本类型
使用类型加中括号定义数组类型:number[]
// 定义数组类型时需要指定类型,如数字类型、字符串类型等,以及任意类型
let arr: number[] = [1, 2, 3];
let arr2: string[] = ["1", "2"];
let arr3: any[] = [1, "2", true]; // 数组中可以存任意类型的元素
// 如果指定的特定的类型,是不允许数组中有除此之外类型的元素的
let arr: number[] = [1,2,3,'1']; // 此时会报错
arr.unshift('1'); //操作方法添加也是不允许的
4.2 数组泛型
规则 Array <类型>
let arr: Array<number> = [1, 2, 3];
4.3 用接口表示数组
一般用来描述类数组。
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
//表示:只要索引的类型是数字时,那么值的类型必须是数字。
4.5 多维数组
let data:number[][] = [[1,2], [3,4]];
4.6 arguments 类数组
function Arr(...args:any): void {
console.log(arguments)
//错误的arguments 是类数组不能这样定义
let arr:number[] = arguments
}
Arr(111, 222, 333)
function Arr(...args:any): void {
console.log(arguments)
//ts内置对象IArguments 定义
let arr:IArguments = arguments
}
Arr(111, 222, 333)
//其中 IArguments 是 TypeScript 中定义好了的类型,它实际上就是:
interface IArguments {
[index: number]: any;
length: number;
callee: Function;
}
5 函数类型
5.1 定义函数类型
// 定义一个函数
const fn = (name: string, age:number): string => {
return name + age
}
// 注意,参数不能多传,也不能少传 必须按照约定的类型来
fn('张三',18)
5.2 函数的可选参数 ?
//通过 ? 表示该参数为可选参数
const fn = (name: string, age?:number): string => {
return name + age
}
fn('张三')
5.3 函数参数的默认值
const fn = (name: string = "我是默认值"): string => {
return name
}
fn()
5.4 接口定义函数
//定义参数 num 和 num2 :后面定义返回值的类型
interface Add {
(num: number, num2: number): number
}
const fn: Add = (num: number, num2: number): number => {
return num + num2
}
fn(5, 5)
interface User{
name: string;
age: number;
}
function getUserInfo(user: User): User {
return user
}
5.5 定义剩余参数 …
const fn = (array:number[], ...items:any[]):any[] => {
console.log(array,items)
return items
}
let a:number[] = [1,2,3]
fn(a,'4','5','6')
5.6 函数重载
重载是方法名字相同,而参数不同,返回类型可以相同也可以不同。
如果参数类型不同,则参数类型应设置为 any。
参数数量不同你可以将不同的参数设置为可选。
function fn(params: number): void
function fn(params: string, params2: number): void
function fn(params: any, params2?: any): void {
console.log(params)
console.log(params2)
}
fn(123)
fn('123',456)
6 类型断言|联合类型|交叉类型
6.1 联合类型 |
// 例如某些身份证号会有 X 存在,即会有数字又有字母,因此可以使用联合类型
// 使用一个竖线 | 来定义联合类型的变量
let ID: number | string = '412722xxx';
函数使用联合类型
const fn = (something:number | boolean):boolean => {
return !!something
}
6.2 交叉类型 &
多种类型的集合,联合对象将具有所有联合类型的所有成员
interface People {
age: number,
height: number
}
interface Man{
sex: string
}
// 使用 & 联合类型
const user = (man: People & Man) => {
console.log(man.age)
console.log(man.height)
console.log(man.sex)
}
user({age: 18,height: 180,sex: 'male'});
6.3 类型断言
使用类型断言推断传入的值是哪个接口的。
语法:值 as 类型
或者 <类型>值
interface A {
run: string
}
interface B {
build: string
}
const fn = (type: A | B): string => {
return type.run
}
//这样写是有警告的应为B的接口上面是没有定义run这个属性的
interface A {
run: string
}
interface B {
build: string
}
const fn = (type: A | B): string => {
return (type as A).run
}
//可以使用类型断言来推断他传入的是A接口的值
注意:类型断言只能够“欺骗” TS的编译器,如果有错误是不能避免的。
反而滥用类型断言可能会导致运行时错误。
使用 any 临时断言
//这样写会报错因为window没有abc这个东西
window.abc = 123;
//可以使用any临时断言在 any 类型的变量上,访问任何属性都是允许的
(window as any).abc = 123;
as const
是对字面值的断言,与const直接定义常量是有区别的
如果是普通类型跟直接const 声明是一样的
const names = '小满'
names = 'aa' //无法修改
let names2 = '小满' as const
names2 = 'aa' //无法修改
// 数组
let a1 = [10, 20] as const;
const a2 = [10, 20];
a1.unshift(30); // 错误,此时已经断言字面量为[10, 20],数据无法做任何修改
a2.unshift(30); // 通过,没有修改指针
类型断言是不具影响力的
在下面的例子中,将 something 断言为 boolean 虽然可以通过编译,但是并没有什么用 并不会影响结果, 因为编译过程中会删除类型断言
function toBoolean(something: any): boolean {
return something as boolean;
}
toBoolean(1);
// 返回值为 1
7 内置对象
JavaScript 中有很多内置对象,它们可以直接在 TypeScript 中当做定义好了的类型。
7.1 ECMAScript 内置对象
Boolean、Number、String、RegExp、Date、Error
let b: Boolean = new Boolean(1)
console.log(b)
let n: Number = new Number(true)
console.log(n)
let s: String = new String('字符串String')
console.log(s)
let d: Date = new Date()
console.log(d)
let r: RegExp = /^1/
console.log(r)
let e: Error = new Error("error!")
console.log(e)
7.2 DOM 和 BOM 内置对象
Document、HTMLElement、Event、NodeList 等
let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
//读取div 这种需要类型断言 或者加个判断应为读不到返回null
let div:HTMLElement = document.querySelector('div') as HTMLDivElement
document.addEventListener('click', function (e: MouseEvent) {
});
//dom元素的映射表
interface HTMLElementTagNameMap {
"a": HTMLAnchorElement;
"abbr": HTMLElement;
"address": HTMLElement;
"applet": HTMLAppletElement;
"area": HTMLAreaElement;
"article": HTMLElement;
"aside": HTMLElement;
"audio": HTMLAudioElement;
"b": HTMLElement;
"base": HTMLBaseElement;
"bdi": HTMLElement;
"bdo": HTMLElement;
"blockquote": HTMLQuoteElement;
"body": HTMLBodyElement;
"br": HTMLBRElement;
"button": HTMLButtonElement;
"canvas": HTMLCanvasElement;
"caption": HTMLTableCaptionElement;
"cite": HTMLElement;
"code": HTMLElement;
"col": HTMLTableColElement;
"colgroup": HTMLTableColElement;
"data": HTMLDataElement;
"datalist": HTMLDataListElement;
"dd": HTMLElement;
"del": HTMLModElement;
"details": HTMLDetailsElement;
"dfn": HTMLElement;
"dialog": HTMLDialogElement;
"dir": HTMLDirectoryElement;
"div": HTMLDivElement;
"dl": HTMLDListElement;
"dt": HTMLElement;
"em": HTMLElement;
"embed": HTMLEmbedElement;
"fieldset": HTMLFieldSetElement;
"figcaption": HTMLElement;
"figure": HTMLElement;
"font": HTMLFontElement;
"footer": HTMLElement;
"form": HTMLFormElement;
"frame": HTMLFrameElement;
"frameset": HTMLFrameSetElement;
"h1": HTMLHeadingElement;
"h2": HTMLHeadingElement;
"h3": HTMLHeadingElement;
"h4": HTMLHeadingElement;
"h5": HTMLHeadingElement;
"h6": HTMLHeadingElement;
"head": HTMLHeadElement;
"header": HTMLElement;
"hgroup": HTMLElement;
"hr": HTMLHRElement;
"html": HTMLHtmlElement;
"i": HTMLElement;
"iframe": HTMLIFrameElement;
"img": HTMLImageElement;
"input": HTMLInputElement;
"ins": HTMLModElement;
"kbd": HTMLElement;
"label": HTMLLabelElement;
"legend": HTMLLegendElement;
"li": HTMLLIElement;
"link": HTMLLinkElement;
"main": HTMLElement;
"map": HTMLMapElement;
"mark": HTMLElement;
"marquee": HTMLMarqueeElement;
"menu": HTMLMenuElement;
"meta": HTMLMetaElement;
"meter": HTMLMeterElement;
"nav": HTMLElement;
"noscript": HTMLElement;
"object": HTMLObjectElement;
"ol": HTMLOListElement;
"optgroup": HTMLOptGroupElement;
"option": HTMLOptionElement;
"output": HTMLOutputElement;
"p": HTMLParagraphElement;
"param": HTMLParamElement;
"picture": HTMLPictureElement;
"pre": HTMLPreElement;
"progress": HTMLProgressElement;
"q": HTMLQuoteElement;
"rp": HTMLElement;
"rt": HTMLElement;
"ruby": HTMLElement;
"s": HTMLElement;
"samp": HTMLElement;
"script": HTMLScriptElement;
"section": HTMLElement;
"select": HTMLSelectElement;
"slot": HTMLSlotElement;
"small": HTMLElement;
"source": HTMLSourceElement;
"span": HTMLSpanElement;
"strong": HTMLElement;
"style": HTMLStyleElement;
"sub": HTMLElement;
"summary": HTMLElement;
"sup": HTMLElement;
"table": HTMLTableElement;
"tbody": HTMLTableSectionElement;
"td": HTMLTableDataCellElement;
"template": HTMLTemplateElement;
"textarea": HTMLTextAreaElement;
"tfoot": HTMLTableSectionElement;
"th": HTMLTableHeaderCellElement;
"thead": HTMLTableSectionElement;
"time": HTMLTimeElement;
"title": HTMLTitleElement;
"tr": HTMLTableRowElement;
"track": HTMLTrackElement;
"u": HTMLElement;
"ul": HTMLUListElement;
"var": HTMLElement;
"video": HTMLVideoElement;
"wbr": HTMLElement;
}
8 class 类
ES6 提供了更接近传统语言的写法,引入 class
类这个概念,作为对象的模板。通过 class 关键字可以定义类。
基本上,ES6 的 class 可以看做只是一个语法糖,它的绝大部分功能,ES5 都可以做到。
新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
8.1 class 定义变量
//定义类
class Person {
name: string
age: number = 0
constructor (name: string,age: number) {
this.name = name
this.age = age
}
run () {
}
}
/* 注意:
1 不允许在 constructor 定义变量,需要在 constructor 上面先声明
2 如果定义了变量不使用,也会报错的,通常给一个默认值或者进行赋值
*/
8.2 类的修饰符
共有三个:
public:不写默认是public,公共的,内部和外部都可以访问
private:私有属性,只能内部访问
protected:
class Person {
public name: string
private age: number
protected some: any
constructor (name: string, ages: number, some: any){
this.name = name
this.age = ages
this.some = some
}
}
let zs = new Person('张三', 19, 1);
zs.name // 可以访问
zs.age // 私有属性,只能在内部访问,不能在外部访问
zs.some // protected 定义的变量也是私有的,只能在内部和继承的子类中访问,不能在外部访问
class Person {
public name:string
private age:number
protected some:any
constructor (name:string,ages:number,some:any) {
this.name = name
this.age = ages
this.some = some
}
run () {
}
}
class Man extends Person{
constructor () {
super("张三",18,1)
console.log(this.some)
}
create () {
console.log(this.some)
}
}
let xiaoman = new Person('小满',18,1)
let man = new Man()
man.some
8.3 static 静态属性和静态方法
class Person {
static nb: string
static aaa () {
return console.log(123467)
}
constructor () {
this.nb // 此时会报错的
this.aaa() // 也会报错
}
}
Person.nb // 可以
Person.aaa()// 可以
// static 静态属性,不能通过 this 去访问,只能通过类名去调用
// static 静态方法,不能通过 this 去调用,也是通过类名去调用
注意:如果两个函数都是 static 静态的,是可以通过 this 互相调用的。
class Person {
static nb: string
static aaa () {
return console.log(123467)
}
static bbb () {
return this.aaa()
}
constructor () {
}
}
8.4 interface 定义类
interface PersonClass {
get(type: boolean): boolean
}
interface PersonClass2{
set():void,
asd:string
}
class A {
name: string
constructor() {
this.name = "123"
}
}
// 使用关键字 implements,后面跟 interface 的名字,多个用都好隔开
// 继承还是用 extends
class Person extends A implements PersonClass,PersonClass2 {
asd: string
constructor() {
super()
this.asd = '123'
}
get(type:boolean) {
return type
}
set () {
}
}
8.5 抽象类
应用场景如果你写的类实例化之后毫无用处此时我可以把他定义为抽象类
或者你也可以把他作为一个基类-> 通过继承一个派生类去实现基类的一些方法
我们看例子,下面这段代码会报错抽象类无法被实例化
abstract class A {
public name:string
}
new A()
例子2
我们在A类定义了 getName 抽象方法但为实现
我们B类实现了A定义的抽象方法 如不实现就不报错
我们定义的抽象方法必须在派生类实现
abstract class A {
name: string
constructor(name: string) {
this.name = name;
}
print(): string {
return this.name
}
abstract getName(): string
}
class B extends A {
constructor() {
super('小满')
}
getName(): string {
return this.name
}
}
let b = new B();
console.log(b.getName());
视频案例
//1. class 的基本用法 继承 和 类型约束
//2. class 的修饰符 readonly private protected public
//3. super 原理
//4. 静态方法
//5. get set
interface Options {
el: string | HTMLElement
}
interface VueCls {
init(): void
options: Options
}
interface Vnode {
tag: string
text?: string
props?: {
id?: number | string
key?: number | string | object
}
children?: Vnode[]
}
class Dom {
constructor() {
```
}
private createElement(el: string): HTMLElement {
return document.createElement(el)
}
protected setText(el: Element, text: string | null) {
el.textContent = text;
}
protected render(createElement: Vnode): HTMLElement {
const el = this.createElement(createElement.tag)
if (createElement.children && Array.isArray(createElement.children)) {
createElement.children.forEach(item => {
const child = this.render(item)
this.setText(child, item.text ?? null)
el.appendChild(child)
})
} else {
this.setText(el, createElement.text ?? null)
}
return el;
}
```
}
class Vue extends Dom implements VueCls {
options: Options
constructor(options: Options) {
super()
this.options = options;
this.init()
}
static version () {
return '1.0.0'
}
public init() {
let app = typeof this.options.el == 'string' ? document.querySelector(this.options.el) : this.options.el;
let data: Vnode = {
tag: "div",
props: {
id: 1,
key: 1
},
children: [
{
tag: "div",
text: "子集1",
},
{
tag: "div",
text: "子集2"
}
]
}
app?.appendChild(this.render(data))
console.log(app);
```
this.mount(app as Element)
}
```
public mount(app: Element) {
document.body.append(app)
}
}
const v = new Vue({
el: "#app"
})
9 元组
9.1 元组的使用
概念:元组 Tuple 是固定数量的不同类型的元素的集合。
如果需要一个固定大小的不同类型值的集合,就可以使用元组。
let arr:[number,string] = [1,'string']
let arr2: readonly [number,boolean,string,undefined] = [1,true,'sring',undefined]
// 元组和集合的不同之处在于:
// 元组中的元素类型可以是不同的,而且数量是固定的。
// 元组的好处在于可以把多个元素作为一个单元传递。
// 如果一个方法需要返回多个值,可以把这多个值作为元组返回,而不需要创建额外的类来表示。
当赋值或访问一个已知索引的元素时,会得到正确的类型:
let arr:[number,string] = [1,'string']
arr[0].length //error
arr[1].length //success
//数字是没有length 的
元组类型还可以支持自定义名称和变为可选的
let a: [x: number, y?: boolean] = [1]
9.2 越界元素
let arr:[number,string] = [1,'string']
arr.push(true)//error
对于越界的元素他的类型被限制为 联合类型(就是你在元组中定义的类型)
let arr: [number, string] = [1, 'string'];
arr.push(false); // 会提示 类型Boolean的参数不能赋值给类型 string|number 的参数
应用场景,例如 定义Excel返回的数据
let excel: [string, string, number, string][] = [
['title', 'name', 1, '123'],
['title', 'name', 1, '123'],
['title', 'name', 1, '123'],
['title', 'name', 1, '123'],
['title', 'name', 1, '123'],
]
10 枚举 enum
JS 中没有枚举的概念。TS 可以使用 enum
关键字定义枚举。
10.1 数字枚举
// ts 定义的枚举中每一个组员默认都是从0开始的
enum Types {
Red,
Green,
Blue
}
// 等同于
enum Types {
Red = 0,
Green = 1,
Blue = 2
}
增长类型
定义的数字枚举,可以指定初始值,其余组员会自动增长。
// 例如 Red 初始化为 1,其余成员会从 1 开始自动增长。
// 即 Types 的 Green 为 2,Blue 为 3
enum Types{
Red = 1,
Green,
BLue
}
10.2 字符串枚举
在字符串枚举里面,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。
enum Types{
Red = 'red',
Green = 'green',
BLue = 'blue'
}
// 由于字符串枚举没有自增长的行为,字符串枚举可以很好的序列化。
10.3 异构枚举
异构枚举即是可以混合字符串和数字成员。
enum Types{
No = "No",
Yes = 1,
}
10.4 接口枚举
// 定义一个枚举 Types
enum Types {
yyds,
dddd
}
// 定义一个接口 A,有个属性 red 值为 Types.yyds
interface A {
red:Types.yyds
}
// 声明对象的时候要遵循这个原则
let obj:A = {
red: Types.yyds
}
10.5 const 枚举
const 声明的枚举会被编译成常量,普通声明的枚举编译完是个对象。
let 和 var 都是不允许声明枚举的。
const enum Types{
No = "No",
Yes = 1,
}
/*
大多数情况下,枚举是十分有效的方案。 然而在某些情况下需求很严格。 为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,我们可以使用 const枚举。 常量枚举通过在枚举上使用 const修饰符来定义
*/
10.6 反向映射
它包含了正向映射( name -> value)和反向映射( value -> name)
注意:不会为字符串枚举成员生成反向映射。
// 定义一个枚举 Enum
enum Enum {
fall
}
let a = Enum.fall;
console.log(a); //0
let nameOfA = Enum[a];
console.log(nameOfA); //fall
11 类型推论|类型别名
11.1 类型推论
1 声明了一个变量,但没有定义类型。
TS 会在没有明确指定类型的时候进行推测,给出一个推测的类型,这就是类型推论。
// 此时 TS 会推论变量 str 是 string 类型
let str = 'LXTL';
// 因此不能够再赋值给别的类型。 此时会报错,不能将 number 分配给 string
str = 123456;
2 声明了变量,没有定义类型,也没有赋值。
这个时候 TS 会推断成 any 类型,可以进行任何操作。
let xiaoxu
xiaoxu = 18
xiaoxu = true
xiaoxu = 'xiaoxu'
11.2 类型别名 type
type 关键之,可以给一个类型定义一个名称,多用于符合类型。
1 定义类型别名
// 定义一个 string 的类型为 str
type str = string
// 使用 str 类型
let s: str = 'LXBM'
2 定义函数别名
type str = () => string
let s: str = () => "我是小满"
3 定义联合类型别名
type str = string | number
let s: str = 123
let s2: str = '123'
4 定义值的别名
type value = boolean | 0 | '213'
let s:value = true //变量s的值 只能是上面value定义的值
11.3 type 和 interface 的区别
1.interface可以继承 type 只能通过 & 交叉类型合并
2.type 可以定义 联合类型 和 可以使用一些操作符 interface不行
3.interface 遇到重名的会合并 type 不行
11.4 type 高级用法
左边的值会作为右边值的子类型遵循图中上下的包含关系
type a = 1 extends number ? 1 : 0 //1
type a = 1 extends Number ? 1 : 0 //1
type a = 1 extends Object ? 1 : 0 //1
type a = 1 extends any ? 1 : 0 //1
type a = 1 extends unknow ? 1 : 0 //1
type a = 1 extends never ? 1 : 0 //0
12 never
12.1 什么是 never
TS 中使用 never
类型来表示不应该存在的状态。
// 返回never的函数必须存在无法达到的终点
// 因为必定抛出异常,所以 error 将不会有返回值
function error(message: string): never {
throw new Error(message);
}
// 因为存在死循环,所以 loop 将不会有返回值
function loop(): never {
while (true) {
}
}
12.2 never 与 void 的差异
//void类型只是没有返回值 但本身不会出错
function Void():void {
console.log();
}
//只会抛出异常没有返回值
function Never():never {
throw new Error('aaa')
}
差异2 :当我们鼠标移上去的时候会发现,只有 void 和 number
never 在联合类型中会被直接移除
type A = void | number | never
12.3 never 类型的应用场景
可用于兜底逻辑。
type A = 'key1' | 'key2' | 'key3'
function fn(value:A) {
switch (value) {
case "key1":
break
case "key2":
break
case "key3":
break
default:
break
}
}
比如另一个同事需要新加一个 key4 ,那么必须手动找到所有 Switch 代码并处理,否则将可能引入 bug。
由于任何类型都不能赋值给 never
类型的变量,所以当存在进入 default
分支的可能性时,TS的类型检查会及时帮我们发现这个问题。
type A = 'key1' | 'key2' | 'key3' | 'key4'
function fn(value:A) {
switch (value) {
case "key1":
break
case "key2":
break
case "key3":
break
default:
//是用于场景兜底逻辑
const error:never = value;
return error
}
}
13 Symbol
自 ECMAScript 2015起,symbol 成为了一种新的原生类型,就像 number 和string 一样。
symbol 类型的值是通过 Symbol 构造函数创建的。
// 可以传递参数作为唯一标识。只支持 number 、string两种类型以及 不传参。
let sym1 = Symbol();
let sym2 = Symbol(123);
let sym3 = Symbol('key');
Symbol 的值是唯一的。
const s1 = Symbol();
const s2 = Symbol();
// s1 === s2 => false 内存地址并不同
用作对象属性的键
let sym = Symbol();
// 定义的 symbol 可以作为对象的属性名
let obj = {
[sym]: "value"
};
console.log(obj[sym]); // "value"
使用 symbol 定义的属性,是不能通过如下方式遍历拿到的:
for in 、Object.keys 、getOwnPropertyNames、JSON.stringfy
const symbol1 = Symbol('666')
const symbol2 = Symbol('777')
const obj1= {
[symbol1]: 'value1',
[symbol2]: 'value2',
age: 19,
sex: '女'
}
// 1 for in 遍历
for (const key in obj1) {
// 注意在console看key,是不是没有遍历到symbol1
console.log(key)
}
// 2 Object.keys 遍历
Object.keys(obj1)
console.log(Object.keys(obj1))
// 3 getOwnPropertyNames
console.log(Object.getOwnPropertyNames(obj1))
// 4 JSON.stringfy
console.log(JSON.stringify(obj1))
可以通过以下方法拿到:Object.getOwnPropertySymbols()、Reflect.ownKeys()
// 1 拿到具体的symbol 属性,对象中有几个就会拿到几个
Object.getOwnPropertySymbols(obj1)
console.log(Object.getOwnPropertySymbols(obj1))
// 2 es6 的 Reflect 拿到对象的所有属性
Reflect.ownKeys(obj1)
console.log(Reflect.ownKeys(obj1))
Symbol.iterator 迭代器 和 生成器 for of
https://blog.csdn.net/qq1195566313/article/details/122463630?spm=1001.2014.3001.5501
14 泛型 ***
泛型,定义动态类型。
14.1 函数泛型
// 定义一个数字类型的函数
function num (a:number,b:number) : Array<number> {
return [a ,b];
}
num(1,2)
// 定义一个字符串类型的函数
function str (a:string,b:string) : Array<string> {
return [a ,b];
}
str('独孤','求败')
两个函数只是类型不同,实现的功能是一样的。这个时候可以使用泛型来优化。
// 函数名称后面跟一个:<参数名>
// 参数名可以随便定义,这里用 T
function Add<T>(a: T, b: T): Array<T> {
return [a,b]
}
// 当使用这个函数的时候,把参数的类型传进去就可以(动态类型)
// 也是将类型写在函数名的后面 <number>
Add<number>(1,2)
Add<string>('1','2')
// 也可以简写,TS 会自动推断
Add(1,2)
Add('1','2')
也可以使用不同的泛型参数名,只要数量上和使用方式上能对应上就可以。
function Sub<T,U>(a:T,b:U):Array<T|U> {
const params:Array<T|U> = [a,b]
return params
}
// 数量上和使用方式上对应
Sub<Boolean,number>(false,1)
14.2 类型别名定义泛型
type A<T> = string | number | T
let a:A<boolean> = true
let a:A<boolean> = 'str'
let a:A<boolean> = 123456
let a:A<null> = null
let a:A<undefined> = undefined
14.3 定义泛型接口
声明接口的时候,在名字后面加一个:<参数>
interface MyInter<T> {
(arg: T): T
}
function fn<T>(arg: T): T {
return arg
}
// 使用的时候传递类型
let result: MyInter<number> = fn
result(123)
14.4 对象字面量泛型
let foo: {
<T>(arg: T): T
}
foo = function <T>(arg:T):T {
return arg
}
foo(123)
14.5 泛型约束
即在类型后面跟一个 extends,再跟一个约束的类型。
// 期望在一个泛型的变量上面,获取其 length 参数。但是,有的数据类型是没有 length 属性的。
function getLegnth<T>(arg:T) {
return arg.length
}
// 这个时候我们就可以使用 泛型约束。
于是,我们就得对使用的泛型进行约束,我们约束其为具有 length 属性的类型,这里我们会用到 interface
interface Len {
length:number
}
function getLegnth<T extends Len>(arg:T) {
return arg.length
}
getLegnth<string>('123')
14.6 使用 keyof 约束对象
其中使用了TS泛型和泛型约束。首先定义了T类型并使用extends关键字继承object类型的子类型,然后使用keyof操作符获取T类型的所有键,它的返回 类型是联合 类型,最后利用extends关键字约束 K类型必须为keyof T联合类型的子类型
function prop<T, K extends keyof T>(obj: T, key: K) {
return obj[key]
}
let o = { a: 1, b: 2, c: 3 }
prop(o, 'a')
prop(o, 'd') //此时就会报错发现找不到
高级用法
interface Data {
name: string
age: number
sex: string
}
// for in
// for(let key in obj)
type Options<T extends object> = {
//[Key in keyof T]?:T[Key]
readonly [Key in keyof T]:T[Key]
}
type B = Options<Data>
14.7 泛型类
声明方法跟函数类似名称后面定义<类型>
使用的时候确定类型new Sub()
class Sub<T>{
attr: T[] = [];
add (a:T):T[] {
return [a]
}
}
let s = new Sub<number>()
s.attr = [1,2,3]
s.add(123)
let str = new Sub<string>()
str.attr = ['1','2','3']
str.add('123')
15 tsconfig.json 文件
这个文件是通过tsc --init
命令生成的
配置详解:
"compilerOptions": {
"incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
"tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
"diagnostics": true, // 打印诊断信息
"target": "ES5", // 目标语言的版本
"module": "CommonJS", // 生成代码的模板标准
"outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
"lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
"allowJS": true, // 允许编译器编译JS,JSX文件
"checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
"outDir": "./dist", // 指定输出目录
"rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构
"declaration": true, // 生成声明文件,开启后会自动生成声明文件
"declarationDir": "./file", // 指定生成声明文件存放目录
"emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件
"sourceMap": true, // 生成目标文件的sourceMap文件
"inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
"declarationMap": true, // 为声明文件生成sourceMap
"typeRoots": [], // 声明文件目录,默认时node_modules/@types
"types": [], // 加载的声明文件包
"removeComments":true, // 删除注释
"noEmit": true, // 不输出文件,即编译后不会生成任何js文件
"noEmitOnError": true, // 发送错误时不输出任何文件
"noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
"importHelpers": true, // 通过tslib引入helper函数,文件必须是模块
"downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
"strict": true, // 开启所有严格的类型检查
"alwaysStrict": true, // 在代码中注入'use strict'
"noImplicitAny": true, // 不允许隐式的any类型
"strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量
"strictFunctionTypes": true, // 不允许函数参数双向协变
"strictPropertyInitialization": true, // 类的实例属性必须初始化
"strictBindCallApply": true, // 严格的bind/call/apply检查
"noImplicitThis": true, // 不允许this有隐式的any类型
"noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
"noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)
"noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
"noImplicitReturns": true, //每个分支都会有返回值
"esModuleInterop": true, // 允许export=导出,由import from 导入
"allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块
"moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
"paths": { // 路径映射,相对于baseUrl
// 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
"jquery": ["node_modules/jquery/dist/jquery.min.js"]
},
"rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
"listEmittedFiles": true, // 打印输出文件
"listFiles": true// 打印编译的文件(包括引用的声明文件)
}
// 指定一个匹配列表(属于自动指定该路径下的所有ts相关文件)
"include": [
"src/**/*"
],
// 指定一个排除列表(include的反向操作)
"exclude": [
"demo.ts"
],
// 指定哪些文件使用该配置(属于手动一个个指定文件)
"files": [
"demo.ts"
]
介绍几个常用的:
1.include
指定编译文件默认是编译当前目录下所有的ts文件
2.exclude
指定排除的文件
3.target
指定编译js 的版本例如es5 es6
4.allowJS
是否允许编译js文件
5.removeComments
是否在编译过程中删除文件中的注释
6.rootDir
编译文件的目录
7.outDir
输出的目录
8.sourceMap
代码源文件
9.strict
严格模式
10.module
默认common.js 可选es6模式 amd umd 等
16 namespace 命名空间
在工作中无法避免全局变量造成的污染,typescript 提供了 namespace 避免这个问题出现。
1 内部空间,主要用于组织代码,避免命名冲突
2 命名空间内的类,默认私有
3 通过 export 暴露
4 通过 namespace 关键字定义
TypeScript与ECMAScript 2015一样,任何包含顶级import
或者export
的文件都被当成一个模块。相反地,如果一个文件不带有顶级的import
或者export
声明,那么它的内容被视为全局可见的(因此对模块也是可见的)
命名空间中通过 export 将想要暴露的部分导出,否则是无法读取其值的。
namespace a {
export const Time: number = 1000
export const fn = <T>(arg: T): T => {
return arg
}
fn(Time)
}
namespace b {
export const Time: number = 1000
export const fn = <T>(arg: T): T => {
return arg
}
fn(Time)
}
a.Time
b.Time
嵌套命名空间
namespace a {
export namespace b {
export class Vue {
parameters: string
constructor(parameters: string) {
this.parameters = parameters
}
}
}
}
let v = a.b.Vue
new v('1')
抽离命名空间
// a.ts
export namespace V {
export const a = 1
}
// b.ts
import {V} from '../observer/index'
console.log(V); //{a:1}
简化命名空间
namespace A {
export namespace B {
export const C = 1
}
}
import X = A.B.C
console.log(X);
合并命名空间
// 重名的命名空间会合并
namespace a {
export const b = 123
}
namespace a {
export const c = 456
}
a.b
a.c
17 三斜线指令
三斜线引用,可以理解成 import,告诉编译器在编译过程中需要引入的额外的文件。
/// <reference path="..." />
指令是三斜线指令最常见的一种。它用于声明文件建的依赖。
例如:
// a.ts
namespace A {
export const fn = () => 'a'
}
// b.ts
namespace B {
export const fn = () => 'a'
}
// index.ts 引入之后直接可以使用变量A
///<reference path="./index2.ts" />
///<reference path="./index3.ts" />
console.log(A);
声明文件引入
例如,把 /// 引入到声明文件,表明这个文件使用了 @types/node/index.d.ts里面声明的名字; 并且,这个包需要在编译阶段与声明文件一起被包含进来。
仅当在你需要写一个d.ts文件时才使用这个指令。
///<reference types="node" />
18 声明文件 d.ts
声明文件 declare
当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
declare var 声明全局变量
declare function 声明全局方法
declare class 声明全局类
declare enum 声明全局枚举类型
declare namespace 声明(含有子属性的)全局对象
interface 和 type 声明全局类型
/// <reference /> 三斜线指令
如果有一些第三方包确实没有声明文件,我们可以自己去定义。
名称.d.ts 创建一个文件去声明
例如:
// index.ts
import express from 'express'
const app = express()
const router = express.Router()
app.use('/api', router)
router.get('/list', (req, res) => {
res.json({
code: 200
})
})
app.listen(9001,()=>{
console.log(9001)
})
// express.d.ts
declare module 'express' {
interface Router {
get(path: string, cb: (req: any, res: any) => void): void
}
interface App {
use(path: string, router: any): void
listen(port: number, cb?: () => void): void
}
interface Express {
(): App
Router(): Router
}
const express: Express
export default express
}
19 Mixins 混入
TypeScript
混入 Mixins
其实vue也有mixins这个东西,你可以把他看作为合并。
19.1 对象混入
ES6 的 Object.assign 合并多个对象
此时 people 会被推断成一个交叉类型 Name & Age & sex
interface Name {
name: string
}
interface Age {
age: number
}
interface Sex {
sex: number
}
let people1: Name = { name: "小满" }
let people2: Age = { age: 20 }
let people3: Sex = { sex: 1 }
const people = Object.assign(people1,people2,people3)
19.2 类的混入
首先声明两个 mixins 类
class A {
type: boolean = false;
changeType() {
this.type = !this.type
}
}
class B {
name: string = '张三';
getName(): string {
return this.name;
}
}
下面创建一个类,结合了这两个 mixins:
首先应该注意到的是,没使用 extends 而是使用 implements
,把类当成了接口。
我们可以这么做来达到目的,为将要mixin,进来的属性方法创建出占位属性。这告诉编译器这些成员在运行时是可用的。这样就能使用 mixin 带来的便利,虽说需要提前定义一些占位属性。
class C implements A,B{
type:boolean
changeType:()=>void;
name: string;
getName:()=> string
}
最后,创建这个帮助函数,帮我们做混入操作。它会遍历 mixins 上的所有属性,并复制到目标上去,把之前的占位属性替换成真正的实现代码。
Object.getOwnPropertyNames()可以获取对象自身的属性,除去他继承来的属性,对它所有的属性遍历,它是一个数组,遍历一下它所有的属性名
Mixins(C, [A, B])
function Mixins(curCls: any, itemCls: any[]) {
itemCls.forEach(item => {
Object.getOwnPropertyNames(item.prototype).forEach(name => {
curCls.prototype[name] = item.prototype[name]
})
})
}
20 装饰器Decorator
21 webPack 构建 TS 项目
22 实战:TS 编写发布订阅模式
/*
on 订阅/监听
emit 发布/注册
once 只执行一次
off 解除绑定
*/
interface 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) {
const callbackList: Array<Function> = this.list[name] || [];
callbackList.push(callback)
this.list[name] = callbackList
}
emit(name: string, ...args: Array<any>) {
let evnetName = this.list[name]
if (evnetName) {
evnetName.forEach(fn => {
fn.apply(this, args)
})
} else {
console.error('该事件未监听');
}
}
off(name: string, fn: Function) {
let evnetName = this.list[name]
if (evnetName && fn) {
let index = evnetName.findIndex(fns => fns === fn)
evnetName.splice(index, 1)
} else {
console.error('该事件未监听');
}
}
once(name: string, fn: Function) {
let decor = (...args: Array<any>) => {
fn.apply(this, args)
this.off(name, decor)
}
this.on(name, decor)
}
}
const o = new Dispatch()
o.on('abc', (...arg: Array<any>) => {
console.log(arg, 1);
})
o.once('abc', (...arg: Array<any>) => {
console.log(arg, 'once');
})
// let fn = (...arg: Array<any>) => {
// console.log(arg, 2);
// }
// o.on('abc', fn)
// o.on('ddd', (aaaa: string) => {
// console.log(aaaa);
// })
//o.off('abc', fn)
o.emit('abc', 1, true, '小满')
o.emit('abc', 2, true, '小满')
// o.emit('ddd', 'addddddddd')