# learn-ts **Repository Path**: lyfcloud/learn-ts ## Basic Information - **Project Name**: learn-ts - **Description**: 学习TS的实践和笔记 - **Primary Language**: TypeScript - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2021-04-18 - **Last Updated**: 2024-12-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README [TOC] ## 一、 获取Typescript #### 1. 安装`Typescript` ```javascript npm install -g typescript ``` #### 2. IDE - `IDE`推荐使用 [VS Code](https://code.visualstudio.com/) - VS Code是开源的,跨终端的轻量级编辑器,内置TS - 本身就是TS编写的,支持力度较好 #### 3. 如何编译TS - 新建一个`index.ts`文件 - 执行代码(每次修改都要执行命令) ```javascript tsc index.ts ``` - 如果要一直监视,首先在当前目录下执行 ```javascript tsc --init // 初始化TS ``` - 生成一个`tsconfig.json`文档,一般会将配置文档中的输出`outDir`的改一下位置,这个按照自己的要求来做 ```javascript "outDir": "./js" ``` - 可以开启VS Code的watch, 位置 `终端---运性任务 --- typescript --- tsc:监视tsconfig.json` ## 二、基础数据类型 - `Javascript`的类型分为两种:原始数据类型和对象类型 - 原始数据类型:`Boolean`、`number`、`string`、`null`、`undefined`、`Symbol`和`BigInt` #### 1. Boolean类型 ```typescript let flag: boolean = false; // 编译结果:var flag = false; ``` #### 2. 数字类型`number` ```typescript let num:number = 0 // 编译结果:var num = 0; ``` #### 3. 字符串`string` ```typescript let name:string = '张三' // 编译结果:var name = '张三'; ``` #### 4. Void空值 ```typescript // Void表示没有任何返回值的函数 function fn():void{ console.log('没有返回值') } ``` #### 5. Array类型 ```typescript // 方式一 let arr: number[] = [1, 2, 3] // 编译结果 var arr = [1, 2, 3]; // 方式二 (泛型方式) let arr: Array = [1,2,3] // 编译结果 var arr = [1, 2, 3]; ``` #### 6. Tuple元组类型 - 在`javascript`中不存在元组,这是TS特有的类型 - 元组中每一个属性都有一个关联类型,初始化元组时必须提供每个属性的值 - 当元素越界,也就是给数组添加元素时,它的类型会被限制为元组中每个类型的联合类型 ```typescript // 联合类型[string, number] let tom: [string, number] = ['Tom', 25]; tom.push('1111') // 成功 tom.push(1111)// 成功 tom.push(null)// 成功 tom.push(undefined)// 成功 tom.push(true) // 失败 Argument of type 'true' is not assignable to parameter of type 'string | number'. ``` #### 7. 枚举类型`Enum` - 用于取值被限定在一定范围内的场景 - 默认情况枚举开始于其成员编号`0`,然后自增 - 也可以通过设置其值来进行更改,也可以设置所有值 ```typescript // TS enum ColorEnum{ red, green, blue } // ES5 编译结果 var ColorEnum; (function (ColorEnum) { ColorEnum[ColorEnum["red"] = 0] = "red"; ColorEnum[ColorEnum["green"] = 1] = "green"; ColorEnum[ColorEnum["blue"] = 2] = "blue"; })(ColorEnum || (ColorEnum = {})); // TS enum ColorEnum1{ red = 1, green = 3, blue } // ES5 编译结果 var ColorEnum1; (function (ColorEnum1) { ColorEnum1[ColorEnum1["red"] = 1] = "red"; ColorEnum1[ColorEnum1["green"] = 3] = "green"; ColorEnum1[ColorEnum1["blue"] = 4] = "blue"; })(ColorEnum1 || (ColorEnum1 = {})); ``` #### 8. Any任意类型值 - 如果定义为any类型,则工作方式变为`js`模式😓 - 任何东西都可以赋值为any - top type/ bottom type ,放弃了类型检查 ```typescript let anyNumber: any = '任意类型值' anyNumber = 0 ``` #### 9. Unknown类型 - `TS3.0` 引入 - top type 和 any一样,所有类型都可以分配给unknown,但unknown不能赋值给所有,只能赋值给any和unknown - unknown 是 top type (任何类型都是它的 subtype) , 而 any 即是 top type, 又是 bottom type (它是任何类型的 subtype ) , 这导致 any 基本上就是放弃了任何类型检查. ```typescript /*任何值都可以为unknown类型*/ let un: unknown un = true // ok un = 0 // ok un = 'string' // ok un = [] // ok un = {} // ok un = null // ok un = undefined // ok un = Symbol('111') // ok /* unknown只能复制给any类型和unknown类型,其他类型编译不成功 */ let value1:unknown = un // ok let value2:any = un // ok let value3:string = un // error let value4:boolean = un // error let value5:number = un // error let value6:any[] = un // error let value7:null = un // error let value8:undefined = un // error ``` #### 10. Null 和 Undefined `TypeScript`里,`undefined`和`null`两者有各自的类型,分别为`undefined`和`null`,与`void`的区别是`undefined`和`null`为所有类型的子类型。 ```typescript let u:undefined = undefined let n:null = null /*前提条件tsconfig.json中 strictNullChecks 设置为false*/ let numb: number = null // 编译通过 /*前提条件tsconfig.json中 strictNullChecks 设置为true*/ let numb: number = null // 编译未通过,Type 'null' is not assignable to type 'number' ``` #### 11. Never类型 - 表示的是那些永不存在的值的类型 - 可以用来使得异常的处理更加安全 - [TypeScript中的never类型具体有什么用?](https://www.zhihu.com/question/354601204) ```typescript function listen() : never{ while(true){ let conn = 1111 } } listen() console.log("!!!") //Error ``` ## 三、 函数 #### 1. 函数的定义 - 函数是`javascript`的一等公民 - 定义函数的方式:`函数声明`和`函数表达式` ```javascript // Javascript // 函数声明 function getInfo(name, age) { return `My name is ${name}, age is ${age}` } // 函数表达式 let getInfo = (name, age) => { return `My name is ${name}, age is ${age}` } ``` - 在`Ts`中函数的输入和输出是有约束的,输入多余的或者少于要求的参数是不被允许的 ```typescript // 函数声明 function getInfo(name:string, age:number):string { return `My name is ${name}, age is ${age}` } // 函数表达式 let getInfo:(name:string, age:number)=> string = (name:string, age:number):string => { return `My name is ${name}, age is ${age}` } console.log(getInfo('张三', 20)) // My name is 张三, age is 20 ``` #### 2. 可选参数 - 输入多余的或者少于的参数是不被允许的 - `?`来表示可选参数 ```typescript let getInfo: (name: string, age?: number) => string = (name: string, age?: number): string => { if (age) { return `My name is ${name}, age is ${age}` } else { return `My name is ${name}, age is 30` } } console.log(getInfo('张三')) // My name is 张三, age is 20 console.log(getInfo('张三', 50)) // My name is 张三, age is 50 ``` - 可选参数必须在参数后面 ```typescript let getInfo: (age?: number, name: string ) => string = (age?: number, name: string ): string => { if (age) { return `My name is ${name}, age is ${age}` } else { return `My name is ${name}, age is 30` } } console.log(getInfo('张三')) // A required parameter cannot follow an optional parameter // 多个可选参数 let getInfo: (name: string, age?: number, sex?: string) => string = (name: string, age?: number, sex?: string): string => { if (age) { return `My name is ${name}, age is ${age}, sex is ${sex}` } else { return `My name is ${name}, age is 30, sex is ${sex}` } } console.log(getInfo('张三')) // My name is 张三, age is 30, sex is undefined console.log(getInfo('张三', undefined, '男')) // My name is 张三, age is 30, sex is 男 ``` #### 3. 默认参数 - TS的默认值设置和ES6的默认值设置是一样的 ```typescript let getInfo: (name: string, age?: number) => string = (name: string, age?: number): string => { if (age) { return `My name is ${name}, age is ${age}` } else { return `My name is ${name}, age is 30` } } console.log(getInfo('张三')) // My name is 张三, age is 30 console.log(getInfo('张三', 20)) // My name is 张三, age is 20 ``` #### 4. 剩余参数 - TS 的剩余参数和ES6的剩余参数是一致的 - 剩余参数只能作为最后一个参数, ```typescript let add: (...rest: number[]) => number = (...rest: number[]): number => { let sum = 0 rest.forEach(item => { sum += item }) return sum } console.log(push(1, 2, 3)) // 6 console.log(push(1, 2, 3, 4)) // 10 // 将剩余参数后面加一个参数,编译的时候会报错,A rest parameter must be last in a parameter list let add: (...rest: number[], a: number) => number = (...rest: number[], a: number): number => { let sum = 0 rest.forEach(item => { sum += item }) return sum } ``` #### 5. 类型推断 - 当在赋值语句的一边指定类型,但另一边没有指定的时候,TS会经过类型推断,自动识别出类型 - 建议当写的时候两边都指定类型,增加可读性 ```typescript // 未左右指定类型 let getInfo:(name:string, age:number)=> string = (name, age) => { return `My name is ${name}, age is ${age}` } let getInfo = (name:string, age:number):string => { return `My name is ${name}, age is ${age}` } // 左右都指定类型 let getInfo:(name:string, age:number)=> string = (name:string, age:number):string => { return `My name is ${name}, age is ${age}` } console.log(getInfo('张三', 20)) // My name is 张三, age is 20 ``` #### 6. 函数重载 - 在`js`中没有函数重载概念,当有相同函数命名时,只会执行最后一个相同名称的函数 ```javascript function testJs (num) { return num } function testJs(num) { return `数字是${num}` } console.log(testJs(1)) // 数字是1 ``` - 在TS中,具有相同函数命名时,会出发函数的重载。 - 函数重载让编译器根据函数的输入决定函数的输出,从而推断出更准确的类型。。 - 将签名更加具体的重载放上面,不那么具体的放后面 ```typescript /** * @description 将鼠标放到num和str上的函数上会得到不同的结果 * num function getInfo(age: number): number (+1 overload) * str function getInfo(name: string): string (+1 overload) */ function getInfo(age: number): number; function getInfo(name: string): string; function getInfo(str: any): any { if (typeof str === 'string') { return 'string' } else { return 0 } } const num = getInfo(1) const str = getInfo('hello') ``` ## 四、类和继承 #### 1. ES5类和继承 - 最简单的类 ```javascript function Person() { this.name = '张三' this.age = 10 } const p = new Person() console.log(p.name) // 张三 ``` - 构造函数和原型链上定义属性和方法 ```javascript function Person() { this.name = '张三' // 定义构造函数的属性 this.age = 10 // 定义构造函数的属性 this.run = function() { // 定义构造函数的方法 console.log(`${this.name}在跑步`) } } Person.prototype.sex = '男' // 原型链上的属性 Person.prototype.work = function() { // 原型链上的方法 console.log(`${this.name}在工作`) } const p = new Person() p.run() // 张三在跑步 p.work() // 张三在工作 ``` - 类的静态方法,创建的是整个class的方法 ```javascript function Person() { this.name = '张三' // 定义构造函数的属性 this.age = 10 // 定义构造函数的属性 this.run = function() { // 定义构造函数的方法 console.log(`${this.name}在跑步`) } } Person.getInfo = function() { console.log('我是静态方法') } Person.getInfo() // 我是静态方法 let p = new Person() p.getInfo() // undefined (因为创建的是整个class的方法,如果想要使用,可以再次进行声明 p.getInfo = Person.getInfo 即可继承) // 例如 // Vue源码中原型链继承和静态方法继承 const Sub = function VueComponent(options) { this._init(options) } Sub.prototype = Object.create(Super.prototype) Sub.prototype.constructor = Sub //静态方法继承 Sub.extend = Super.extend Sub.mixin = Super.mixin Sub.use = Super.use ``` - ES5的继承一共分为两种:对象冒充继承 和原型链继承 - 对象冒充继承可以继承构造函数里面上的属性和方法, 不能继承原型链上面的属性和方法 - 原型链继承可以继承构造函数里面和原型链上的属性和方法,但是实例化子类的时候没法给子类传递参数 ```javascript // 对象冒充继承 function Person() { this.name = '张三' // 定义构造函数的属性 this.age = 10 // 定义构造函数的属性 this.run = function() { // 定义构造函数的方法 console.log(`${this.name}在跑步`) } } Person.prototype.sex = '男' // 原型链上的属性 Person.prototype.work = function() { // 原型链上的方法 console.log(`${this.name}在工作`) } function Web() { /*对象冒充实现继承*/ Person.call(this) } let w = new Web() w.run() // 张三在跑步 w.work() // w.work is not a function 不能继承原型链上的属性和方法 ``` ```javascript // 原型链实现继承, 但是不能给父类传值 /*在不传递参数的情况下,即name:'张三'和 age: 10 规定后*/ function Person() { this.name = '张三' // 定义构造函数的属性 this.age = 10 // 定义构造函数的属性 this.run = function() { // 定义构造函数的方法 console.log(`${this.name}在跑步`) } } Person.prototype.sex = '男' // 原型链上的属性 Person.prototype.work = function() { // 原型链上的方法 console.log(`${this.name}在工作`) } function Web() {} Web.prototype = new Person() let w = new Web() w.run() // 张三在跑步 w.work() // 张三在工作 ``` ```javascript /*在传递参数的情况下,即name和 age需要传值时*/ function Person(name, age) { this.name = name // 定义构造函数的属性 this.age = age // 定义构造函数的属性 this.run = function() { // 定义构造函数的方法 console.log(`${this.name}在跑步`) } } Person.prototype.sex = '男' // 原型链上的属性 Person.prototype.work = function() { // 原型链上的方法 console.log(`${this.name}在工作`) } function Web() {} Web.prototype = new Person() let w = new Web('张三', 20) w.run() // undefined在跑步 w.work() // undefined在工作 ``` - 原型链 + 构造函数的组合继承模式 ```javascript function Person(name, age) { this.name = name // 定义构造函数的属性 this.age = age // 定义构造函数的属性 this.run = function() { // 定义构造函数的方法 console.log(`${this.name}在跑步`) } } Person.prototype.sex = '男' // 原型链上的属性 Person.prototype.work = function() { // 原型链上的方法 console.log(`${this.name}在工作`) } function Web(name, age) { // 对象冒充继承 实例化子类给父类 Person.call(this, name, age) } Web.prototype = new Person() // (这段代码也可以替换为 Web.prototype = Person.prototype 因为对象冒充的时候已经继承构造函数的属性和方法,所以只需要继承父类原型链上的属性和方法即可) let w = new Web('张三', 20) w.run() // 张三在跑步 w.work() // 张三在工作 ``` #### 2. Ts中的类和继承 - 类的定义 - 定义类的关键词class - 字段---类声明的变量,表示对象的相关数据 - 构造函数---实例化时调用,可以为类的对象分配内存 - 方法 --- 为对象要执行的操作 ```typescript class Person { name: string // 字段 constructor(name: string) { // 构造函数 this.name = name } run():void{ // 方法 console.log(`${this.name}在跑步`) } } const p = new Person('张三') p.run() // 张三在跑步 ``` - 继承 - 继承使用关键词extends、super - 子类除了不能继承父类的私有成员(属性和方法)和构造函数,其它的都能继承 - TS一次只能继承一个类,不能继承多个类,但是支持多重继承(A继承B,B继承C) - 子类可以定义自己的属性和方法 - 当子类定义具有和父类相同名称的属性和方法时,将会执行子类的属性和方法 ```typescript class Person { name: string // 字段 constructor(name: string) { // 构造函数 this.name = name } run():void{ // 方法 console.log(`${this.name}在跑步`) } } class Web extends Person{ /* 此处构造函数可以省略, 当没有构造函数的时候TS会自动创建 */ num: number constructor(name: string, num: number) { super(name) this.num = num } // 子类可以定义自己的属性和方法 work():void{ console.log(`${this.name}工作了${this.num}小时`) } } const p = new Web('张三') p.run() // 张三在跑步 p.work() // 张三在工作 ``` - 类的修饰符 `public`、`protected`、`private`、`readonly` | 名称 | 作用 | | --------- | ------------------------------------------------------------ | | public | 当未指明类型的时候的默认类型,可以在任何地方访问 | | protected | 只能被类内部,继承类的内部访问,不能被外部访问 | | private | 只能被内部访问,其它都不能访问 | | readonly | 只读属性,相当于const,只能在声明时或构造函数里被初始化赋值,其它地方不允许赋值 | ```typescript class Person { private name: string protected age: number public sex: boolean readonly height: number = 100 constructor(height) { this.height = height } setHeight() { this.height = 200 // Cannot assign to 'height' because it is a read-only property. } } /*外部访问*/ const p = new Person() console.log(p.name) // Property 'name' is private and only accessible within class 'Person'. console.log(p.age) // Property 'age' is protected and only accessible within class 'Person' and its subclasses. console.log(p.sex) // success class Web extends Person{ getInfo():void{ console.log(this.name) // Property 'name' is private and only accessible within class 'Person'. console.log(this.age) // success console.log(this.sex) // success } } ``` - 静态 `static`, 只能通过类名来进行调用, 静态类型的方法,只能访问静态类的属性, 不能通过实例化来获取 ```typescript class Person { name: string static age: number = 10 constructor(name:string) { this.name = name } static getAge():void { console.log(this.age) // console.log(this.name) // Property 'name' does not exist on type 'typeof Person' } } console.log(Person.age) // 10 Person.age = 1 Person.getAge() // 1 ``` - 多态 - 父类定义一个方法不去实现,让继承它的子类来实现,而且每一个子类都有不同的表现 - 多态属于继承 ```typescript class Animal { name: string constructor(name: string) { this.name = name } eat() { return this.name + '吃东西' } } class Dog extends Animal{ constructor(name: string) { super(name) } eat() { return this.name + '吃狗粮' } } class Cat extends Animal { constructor(name: string) { super(name) } eat() { return this.name + '吃猫粮' } } const d = new Dog('小狗') console.log(d.eat()) // 小狗吃狗粮 const c = new Cat('小猫') console.log(c.eat()) // 小猫吃猫粮 ``` - 抽象类`abstract` - 抽象类做为其它子类的基类使用,一般不会被实例化 - `abstract` 用于定义抽象类和在抽象类内部定义抽象方法 - 在子类的构造函数中必须调用`super()`方法 ```typescript abstract class Person { name: string constructor(name: string) { this.name = name } abstract run():unknown // 必须在子类中实现 } class Web extends Person{ run():void{ console.log(`${this.name}在运动`) } } ``` - 存取器 - TS 支持 通过getters/setters来截取对象成员的访问 - set 中定义的方法不能定义返回值 ```typescript class Person { name: string constructor(name: string) { this.name = name } get userName():string { console.log(`get ${this.name}`) return this.name } set userName(name: string) { console.log(`set ${this.name}`) this.name = name } } const p = new Person('李四') p.userName // get 李四 p.userName = '王五' // set 王五 p.userName // get 王五 ``` ## 五、接口 > 接口主要是定义行为和动作的规范 > 作用是为这些类名和你的代码或者第三方代码定义契约 > 传入的对象参数实际上好汉很多属性,但是编译器只会检查哪些必需的属性是否存在 > 只要传入的对象满足上面提到的必要条件那么他是被允许的 #### 1. 属性接口(对json的定义) - 对批量传入参数的写法进行约束 ```typescript interface LabelObj { label: string } function printLabel(labelObj: LabelObj){ console.log(labelObj.label) console.log(labelObj.size) // 类型“labelObj”上不存在属性“size” } let myObj = { size: 10, // 在接口层未定义,即使传入进去,在方法层printLabel() 也不能使用 label: 'Size 10 Object' } printLabel(myObj) // 当另一个函数的传参和这个函数的传参相同时,所定义的interface是相同的, 因此可以共用一个interface ``` #### 2. 可选属性 - 接口中的属性不全都是必需的, 有些只是在某些条件下,或根本不存在 - 带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义后面加上`?`符号 - 好处:一是对可能存在属性进行预定义,二是可以捕获引用了不存在属性时的错误 ```typescript interface SquareConfig{ color?: string width?: number } function createSquare(config: SquareConfig): { color: string, area: number } { let newSquare = { color: 'white', area: 100 } if (config.width) { newSquare.width = config.width } if (config.width) { newSquare.area = config.width * config.width } return newSquare } let mySquare = createSquare({ color: 'black' }) console.log(mySquare) // { color: 'black', area: 100 } ``` - 可选参数封装ajax ```javascript $.ajax({ type: 'GET', url: 'test.json', data: { username: $('#username').val(), content: $('#content').val()}, dataType: 'json' }) ``` ```typescript interface Config{ type: string url: string data?: string dataType: string } function ajax(config: Config) { const xhr = new XMLHttpRequest() xhr.open(config.type, config.url, true) xhr.send(config.data) xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { console.log('成功') if(config.dataType === 'json') { console.log(JSON.parse(xhr.responseText)) } else { console.log(xhr.responseText) } } } } ajax({ type: 'GET', url: 'xxxx.com', data: 'name = 1111', dataType: 'json' }) ``` #### 3. 只读属性`readonly` - 一些对象属性只能在对象刚刚创建的时候修改其值 ```typescript interface Point{ readonly x: number readonly y: number } let p: Point = { x: 10, y: 20 } p.x = 100 // 无法分配到 "x" ,因为它是只读属性 ``` #### 4. 函数类型参数 - 对于函数类型的类型检查,函数的参数名可以不与接口里定义的参数名一致 ```typescript interface Encrypt{ (key: string, value: string):string } let md5: Encrypt = function(key: string, value: string):string{ return key + value } // console.log(md5('哈哈哈', '呵呵呵')) // 哈哈哈呵呵呵 // 对于函数类型的类型检查,函数的参数名可以不与接口里定义的参数名一致 let sha1: Encrypt = function(a: string, b: string):string{ return a + b } // console.log(sha1('哈哈哈', '呵呵呵')) // 哈哈哈呵呵呵 ``` #### 5. 混合类型 ```typescript interface Counter{ (start: number): string interval: number reset(): void } function getCounter(): Counter{ const counter = function (start: number) {} counter.interval = 1 counter.reset = () => {} return counter } ``` #### 6. 可索引接口 ```typescript // 对数组类型的约束 interface UseArr{ [index: number]: string } const arr: UseArr = ['111', '2222'] const arr: UseArr = [111, 222] // 不能将类型“number”分配给类型“string” // 对对象类型的约束 interface UseObj{ [index: string]: string } const obj: UseObj = { name: '张三', age: 10 // 不能将类型“number”分配给类型“string” } ``` #### 7. 类类型接口的约束 - 当类类型的`interface`被定义的时候,`class`类中必须包含`interface`中的属性和方法 - 使用`implements`关键字来链接 ```typescript interface Animal{ name: string eat(str: string): void } class Dog implements Animal{ name: string constructor(name: string) { this.name = name } eat() { console.log(`${this.name}哈哈哈`) } } const d = new Dog('小狗') d.eat() // 小狗哈哈哈 class Cat implements Animal{ name: string constructor(name: string) { this.name = name } eat() { console.log(`${this.name}啦啦啦`) } } const c = new Cat('小猫') c.eat() // 小猫啦啦啦 ``` #### 8. 接口的扩展,接口可以继承接口`extends` ```typescript interface Animal { eat(): void } // 当类继承多个接口时 interface Person extends Animal AAA,BBB interface Person extends Animal{ work():void } // 当类继承多个接口时 class Web implements Person, AAA,BBB class Web implements Person { name: string constructor(name) { this.name = name } // eat 和 work 必须要实现 eat() { console.log(`${this.name}东吃西`) } work() { console.log(`${this.name}东吃西`) } } const p = new Web('小明') p.eat() // 小明吃东西 p.work() // 小明在工作 ``` #### 9. 实现类的继承和接口继承 ```typescript interface Animal { eat(): void } interface Person extends Animal{ work():void } class Web { name: string constructor(name: string) { this.name = name } coding() { console.log(`${this.name}喜欢写代码`) } } class Programer extends Web implements Person { constructor(name: string) { super(name) } eat() { console.log(`${this.name}东吃西`) } work() { console.log(`${this.name}在工作`) } } const p = new Programer('小明') p.eat() // 小明吃东西 p.work() // 小明在工作 p.coding() // 小明喜欢写代码 ``` ## 六、类型别名 #### 1. 定义 - 类型别名是给一个类型起一个新的名字,并不会建立新的类型 - 类型别名可以用于原始值、联合类型、交叉类型、元组和其它任何需要手写的类型 - 错误信息、鼠标悬停时,不会使用别名,而是直接显示为引用类型 ```typescript const greet = (uid: string | number, name: string) => { console.log(`${name} --- ${uid}`) } const greetUser = (user: { uid: string | number, name: string }) => { console.log(`${user.name} --- ${user.uid}`) } // 这两个函数中重复的有 string | number和 {uid: string | number, name: string}, 因此可以定义类型别名 // 当类型需要重复时, 或者多行写,就需要类型的别名来定义 type Uid = string | number type User = { uid: Uid name: string } const greet = (user: User) => { console.log(`${user.name} --- ${user.uid}`) } const obj = { uid: 11111111, name: '李四' } greet(obj) // 李四---11111111 ``` #### 2. 关键字 - `extends`可以继承一个类,也可以用来继承interface, 还可以用来判断类型 ```typescript T extends U ? X : Y // 解读: 如果T能赋值给U, 则类型为X 否则为Y // 简单例子 type Words = 'a' | 'b' | 'c' type W = T extends Words ? true | false type WA = W<'a'> // true type WD = W<'D'> // false // 联合类型 type A = T extends 'x' ? 'a' : 'b' type B = 'x' | 'y' type C = A // 'a' | 'b' ``` - `typeof`判断一个变量的基础类型,在TS中可以获取一个变量的声明类型,如果不存在,则获取该类型的推论类型 ```typescript // 声明类型 interface Person { name: string age: number location?: string } const jackt: Person = { name: 'jack', age: 10 } type jack = typeof jack // Person // 推论类型 function foo(x: number):number[] { return [x] } type F = typeof foo // (x: number) => number[] /* 可以用来取一个对象接口的所有key值 */ type K1 = keyof Person // 'name' | 'age' | 'location' type K2 = keyof Person[] // number | "length" | "toString" | "toLocaleString" | "pop" | "push" | "concat" | "join" | "reverse" | "shift" | "slice".....等等 type K3 = keyof { [x: string]: Person } // string | number /* 对基本类型的判断*/ let key1: keyof boolean // "valueOf" let key2: keyof number // "valueOf" | "toString" | "toFixed" | "toExponential" | "toPrecision" | "toLocaleString" let key3: keyof string // number | "valueOf" | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring".... let key4: keyof null // never let key5: keyof undefined // never let key6: keyof symbol // "valueOf" | "toString" let key7: keyof bigint // never /* 对引用类型的判断*/ let key8: keyof Array // number | "toString" | "toLocaleString" | "concat" | "indexOf" | "lastIndexOf" | "slice" | "length" | "includes" | "pop" | "push" | "join" ..... let key9: keyof Function // "toString" | "length" | "apply" | "call" | "bind" | "prototype" | "arguments" | "caller" | "name" let key10: keyof Object // "valueOf" | "toString" | "toLocaleString" | "constructor" | "hasOwnProperty" | "isPrototypeOf" | "propertyIsEnumerable" // typeof 和 keyof 的结合 const Colors = { red: "11", blue: "22", } type Color = keyof typeof Colors // 'red' | 'blue' let color: Color color = 'red' // success color = 'blue' // success color = 'black' // error // 先执行typeof Colors 获取到变量类型, 然后通过keyof 获取该类型的所有值 ``` - `in` 遍历枚举类型 ```typescript type Keys = 'a' | 'b' type obj = { [P in Keys]: number } // { a: number; b: number; } ``` - `infer`只能在`extends`条件类型语句中使用, 可以声明一个类型变量并且对它进行使用,可以用来获取函数的返回值类型, ```typescript type ParamType = T extends (param: infer P) => any ? P : T; interface User { name: string; age: number; } type Func = (user: User) => void; type Param = ParamType; // User ``` #### 3.`TS`内置类型别名 [文档](https://www.typescriptlang.org/docs/handbook/utility-types.html#intrinsic-string-manipulation-types) - 源码位置**node_modules/typescript/lib/lib.es5.d.ts** - `Partial` 将某个类型里面的属性全部变为可选项? ```typescript type Partial = { [P in keyof T]?: T[P]; }; /从源码中可以看到 keyof T 是先拿到T的所有属性, 然后in进行遍历, 将赋值其赋值给P, 然后T[P]获取到相应的属性值,结合?则变为可选项 type A = { name: string age: number } type B = Parital // { name?:string, age?: number } ``` - `Required`的作用和*Partial*的作用相反,将所有类型变为必选项 ```typescript type Required = { [P in keyof T]-?: T[P]; }; // -?代表的是移除?标识,所有的为必填项 type A = { name?: string age?: number } type B = Parital // { name:string, age: number } ``` - `Readonly` 将传入的值变为只读选项 ```typescript type Readonly = { readonly [P in keyof T]: T[P]; }; type A = { name: string age?: number } type B = Readonly // {readonly name: string; readonly age?: number;} ``` - `Pick` 将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型 ```typescript // K必须是T的key // 选出T类型中的K 为T 的子类型 type Pick = { [P in K]: T[P]; }; type A = { name: string age: number } type C = Pick // { name: string; } ``` - `Record`将K中所有的属性的值转化为T类型 ```typescript type Record = { [P in K]: T; } type T = { name: string, age: number } type K = string | number type C = Record // { [x: string]: T; [x: number]: T;} type K = '1' | 2 | 1 type C = Record // { 1: T, 2: T }; // 注意:实践中发现数字1会和字符串1合并 ``` - `Exclude` 将某个类型中属于另一个的类型移除掉 ```typescript // 去除T中和U相同的类型,返回T type Exclude = T extends U ? never : T; type A = '1' | '2' | '4' type B = '2' | '5' | '6' type C = Exclude // "1" | "4" type A = string | number type B = string | bigint type C = Exclude // number ``` - `Extract` 中提取出T包含在U的元素, 也就是 T 和 U 的交集 ```typescript type Extract = T extends U ? T : never; type A = '1' | '2' | '4' type B = '2' | '5' | '6' type C = Extract // "2" type A = string | number type B = string | bigint type C = Extract // string ``` - `ReturnType` 获取函数的返回类型 ```typescript type ReturnType any> = T extends (...args: any[]) => infer R ? R : any; function A(x: number): number[] { return [x] } type B = typeof A // (x: number) => number[] type fn = ReturnType // number[] ``` - `InstanceType`获取构造函数类型的返回类型 ```typescript type InstanceType any> = T extends new (...args: any) => infer R ? R : any; class A { name: string age: number constructor(name: string, age: number) { this.name = name this.age = age } } type B = InstanceType // A ``` - `ThisType`上下文此类型的标记 必须启用--noImplicitThis标志才能使用此实用程序 ```typescript interface ThisType { } /* 例子 */ type A = { data?: D, methods?: M & ThisType } function makeObj(desc: A): D & M { let data: object = desc.data || {} let methods: object = desc.methods || {} return { ...data, ...methods } as D & M } let obj = makeObj({ data: { x: 0, y: 0 }, methods: { add(dx: number, dy: number) { this.x += dx this.y += dy } } }) // 经过类型推导可以发现 // D ---> { x: 0, y: 0 } // M ----> { add(dx: number, dy: number) { this.x += dx; this.y += dy;}} // 所以最终合并的应该是对象合并 // { x: 0, y: 0, add(dx: number, dy: number) { this.x += dx; this.y += dy;} } obj.x = 10 obj.y = 5 obj.add(5,5) console.log(obj) // {x: 15, y: 10, add: ƒ} ``` - `Uppercase`将字符串文字类型转换为大写 - `Lowercase`将字符串文字类型转换为小写 - `Capitalize`将字符串文字类型的第一个字符转换为大写 - `Uncapitalize`将字符串文字类型的第一个字符转换为小写 ```typescript type Uppercase = intrinsic; type Lowercase = intrinsic; type Capitalize = intrinsic; type Uncapitalize = intrinsic; type A = 'aaa' type B = Uppercase // "AAA" type C = Lowercase // "aaa" type D = Capitalize // "Aaa" type E = Uncapitalize // "aAA" ``` - `NonNullable`这个类型可以用来过滤类型中的 `null` 及 `undefined`类型 ```typescript type NonNullable = T extends null | undefined ? never : T; type A = string | undefined | null | bigint | number type B = NonNullable // string | number | bigint ``` - `Parameters`可以获得函数的参数类型组成的元组类型 ```typescript type Parameters any> = T extends (...args: infer P) => any ? P : never; function Fn(name: string): string{ return name } type A = Parameters // [name: string] ``` - `ConstructorParameters`在元组中获取构造函数类型的参数 ```typescript type ConstructorParameters any> = T extends new (...args: infer P) => any ? P : never; class Person { name: string age: number constructor(name: string, age: number) { this.name = name this.age = age } } type B = ConstructorParameters // [name: string, age: number] ``` #### 4. 自定义类型别名 - `Omit`构造一个类型为T的,但是类型K除外 ```typescript type Omit = Pick>; type A = { name: string age: number } type B = 'name' type C = Exclude // "age" type D = Pick // age: number; type E = Omit // age: number; ``` - `Mutable`将T中所有属性的readonly移除 ```typescript type Mutable = { -readonly [P in keyof T]: T[P] } type A = { readonly name: string readonly age: number flag: boolean } type B = Mutable // { name: string; age: number; flag: boolean;} ``` - `PowerPartial` 就是通过类型递归,将对象类型中的每一个 key ,都变成了不必须, 主要是为了解决内置的`Partial`的局限性,只能处理第一层的属性 ```typescript type PowerPartial = { // 如果是Object,则递归 [U in keyof T]?: T[U] extends object? PowerPartial : T[U] } // 使用Partial时 // 虽然已经写了C类型的属性可选但是当多层嵌套的时候,在第二层如果name、age或者list未写会报错 type A = { name: string age: number list: A[] } type C = Partial const a: C = { name: '1', age: 0, list: [ { name: '2', age: 0, list: [] } ] } // 使用PowerPartial则可以将子集属性也变成可选属性 type D = PowerPartial const b: D = { name: '1', age: 0, list: [ { name: '2', list: [] } ] } ``` - `Defferred` 相同的属性名称,但使值是一个`Promise`,而不是一个具体的值: ```typescript type Deferred = { [P in keyof T]: Promise; }; type A = { name: string, age: number } type C = Deferred // { name: Promise; age: Promise;} ``` - `Proxify`为 T 的属性添加代理 增加一个`get `和`set`方法 ```typescript type Proxify = { [P in keyof T]: { get(): T[P]; set(v: T[P]): void } }; type A = { name: string, age: number } type C = Proxify // { // name: { // get(): string; // set(v: string): void; // }; // age: { // get(): number; // set(v: number): void; // }; // } ``` #### 5. `interface` VS `type` > - Type aliases and interfaces are very similar, and in many cases you can choose between them freely. Almost all features of an interface are available in type, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable. > > - An interface can have multiple merged declarations, but a type alias for an object type literal cannot. > - [文档](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces) - 相同点 - 都可以描述一个对象或者函数 ```typescript interface User { name: string } interface Fn { (name: string, age: number): void } type User = { name: string } type Fn = (name: string, age: number) => void ``` - 都可以进行扩展 ```typescript // interface 扩展 interface interface A { name: string } interface A { age: number } // interface 扩展 type type A { name: string } interface B extends A {} // type 扩展 type type A = { name: string } type B = { age: number } type C = A & B // type 扩展 interface interface A { name: string } type B = { age: number } type C = A & B ``` - 不同点 - 类型别名不能参与声明合并,但是接口可以, 即类型别名不可以重复命名,但是接口可以 ```typescript // interface interface A { name: string } interface A{ // ok age: number } // type类型 type A = { name: string } type A = { // 标识符“B”重复 age: number } ``` - 接口仅仅可以声明对象类型的,不能声明基础类型的数据 ```typescript // 声明对象类型 interface A { name: string } type B = { name: string } type C = string // ok interface A extends string{} // // “string”仅表示类型,但在此处却作为值使用 ``` - type 可以声明基本类型、联合类型和 元组等类型, 接口不行 ```typescript type A = string // 基本类型 type B = string | number // 联合类型 type C = [string, number] // 元组 ``` - type可以根据typeof来判断出实例类型 ```typescript let div = document.createElement('div') type A = typeof div // HTMLDivElement ``` ## 七、泛型 #### 1. 泛型定义 - 创建一个可重用的组件,支持多种类型的数据 ```typescript /* 希望定义一个传递的参数和返回的参数相同的函数 */ // number function identity(arg: number):number{ return arg } // string function identity(arg: string): string{ return arg } // 使用any也可以达到以上目的, 但是缺失了ts代码检查的功效, 变成anyscript function identity(arg: any): any{ return arg } ``` - 在上述例子中会造成多个函数 或者在函数中增加很多类型判断,因此在Ts中引入了泛型概念,用于捕获传入的类型,然后使用当做返回值的类型 - 具体什么类型是调用这个方法的时候决定的 ```typescript // 方式一 // 定义参数属性 function identity(arg: T):T{ return arg } console.log(identity(111)) // 111 console.log(identity('哈哈哈')) // 哈哈哈 // 方式二 // 直接使用类型推论 console.log(identity(111)) // 111 console.log(identity('哈哈哈')) // 哈哈哈 ``` - 用法: 当你的函数、接口或者类 - 需要作用到很多类型的时候 - 需要被用到很多地方的时候 #### 2. 泛型变量 - 使用泛型创建泛型函数时,编译器要求函数体必须使用通用的类型,也就是这些参数必须是任意的或者所有类型 ```typescript function identity(arg: T): T { // console.log(arg.length) //类型“T”上不存在属性“length” // 并不是所有的类型都是具有length属性的,比如number类型, 因此会报错 return arg } ``` - 如果想要传入一个数组类型格式的 ```typescript function identity(arg: T[]): T[]{ console.log(arg.length) // 数组格式是有length属性的 return arg } // 等价于 function identity(arg: Array): Array{ console.log(arg.length) // 数组格式是有length属性的 return arg } console.log(identity(123)) // 类型“number”的参数不能赋给类型“unknown[]”的参数 console.log(identity([123])) console.log(identity('123')) // 类型“string”的参数不能赋给类型“unknown[]”的参数 ``` #### 3. 泛型接口 ```typescript // 定义接口 /* 方式一 */ interface IdentityFn{ (arg: T):T } /* 方式二 */ interface IdentityFn{ (arg: T): T } // 定义函数 function identity(arg: T):T{ return arg } const myIdentity: IdentityFn = identity console.log(myIdentity(123)) // 123 ``` #### 4. 泛型约束 - 使用关键字`extends`实现约束 ```typescript interface Obj{ length: string } function identity(arg: T):T{ console.log(arg.length) return arg } identity(10) // 类型“number”的参数不能赋给类型“Obj”的参数 identity([1, 2, 3]) // ok identity({length: 10}) // ok ``` #### 5. 泛型类 ```typescript // 定义一个公共的泛型类MySqlDB, 然后User和Article可以一起使用公共的泛型 class MySqlDB{ // 定义一个新增接口 add(info:T):boolean { console.log(info) return true } } // 定义User类 class User{ username: string | undefined password: string | undefined } let u = new User() u.username = '张三' u.password = '123456' let db = new MySqlDB() db.add(u) // User {username: "张三", password: "123456"} class Article{ title: string | undefined desc: string | undefined status: number | undefined constructor(params: { title: string | undefined, desc: string | undefined, status: number | undefined }) { this.title = title this.desc = tesc this.status = status } } const a = new Article({ title: 'Test', desc: '123465', status: 1 }) let dbA = new MySqlDB
dbA.add(a) // Article {title: "Test", desc: "123465", status: 1} ``` #### 6. 泛型函数嵌套 #### 7. 泛型递归 #### 8. Bonus接口智能提示 ```typescript // 可以尝试以下此段代码,会得到不一样的效果 interface Seal{ name: string age: number } interface API{ '/user': { name: string, age: number } '/seal': { seal: Seal } } const api = (url: URL): Promise => { return fetch(url).then((res) => res.json()) } api('/seals').then(res => res.seal) ``` ## 八、`namescpace`命名空间 #### 1. 定义 - 为了解决重名问题 - 定义了标识符的可见范围,一个标识符可以在多个命名空间中定义,它在不通命名空间是互不相同的,这样一个新的命名空间中可以定义任何标识符,他们不会与任何已有的标识符发生冲突,因为已有的定义都处于其它命名空间中 ```typescript namespace A { export function B(arg:string):void { console.log(arg) } export function C(arg:string):void { console.log(arg) } } A.B('B') // B A.C('C') // C ``` - `namespace` 可以嵌套 ```typescript namespace A { export function B(arg:string):void { console.log(arg) } export function C(arg:string):void { console.log(arg) } export namespace D{ export function B(arg:string):void { console.log(`D---${arg}`); } export function C(arg:string):void { console.log(`D---${arg}`); } } } A.B('B') // B A.C('C') // C A.D.B("B"); // D---B A.D.C("C") // D---C ``` - 当在`namespace`调用函数时 ```typescript // 没有相同的函数,会执行上级的函数 namespace A { export function B(arg:string):void { console.log(arg) } export function C(arg:string):void { console.log(arg) } export namespace D{ B('BBBBBB') // BBBBBB } } // 具有相同函数会执行命名空间的函数 namespace A { export function B(arg:string):void { console.log(arg) } export function C(arg:string):void { console.log(arg) } export namespace D{ B('BBBBBB') // D---BBBBBB export function B(arg: string): void { console.log(`D---${arg}`); } } } ``` ## 九、模块 #### 1. 定义 - 模块在其自僧的作用域中,而不是在全局作用域里 - 定义一个模块里的变量,函数,类等等在模块外部是不可见的,除非能够非常明确的使用`export`形式导出它们。相反,如果想使用其它模块导出的变量等,你必须要导入它们,可以使用`import` - 模块是自声明的,两个模块之间的关系是通过在文件级别上使用`import`和`export`建立的