# typescript学习笔记 **Repository Path**: samuelsue/typescript-learning-notes ## Basic Information - **Project Name**: typescript学习笔记 - **Description**: 自学typescript的笔记 - **Primary Language**: TypeScript - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 5 - **Forks**: 2 - **Created**: 2020-09-15 - **Last Updated**: 2024-10-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 配置 ts 运行环境 需要安装的依赖 ```json "devDependencies": { "@typescript-eslint/eslint-plugin": "^3.1.0", "@typescript-eslint/parser": "^3.1.0", "clean-webpack-plugin": "^3.0.0", "eslint": "^7.2.0", "html-webpack-plugin": "^4.3.0", "ts-loader": "^7.0.5", "webpack": "^4.43.0", "webpack-cli": "^3.3.11", "webpack-dev-server": "^3.11.0", "webpack-merge": "^4.2.2" } ``` ```js // webpack.config.js const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') const { CleanWebpackPlugin } = require('clean-webpack-plugin') module.exports = { entry: './src/index.ts', output: { filename: 'main.js', path: path.resolve(__dirname, '../dist'), }, resolve: { extensions: ['.js', '.ts', '.tsx'], }, module: { rules: [ { oneOf: [ { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/, }, ], }, ], }, devtool: process.env.NODE_ENV === 'development' ? 'eval-source-map' : 'cheap-module-source-map', plugins: [ new HtmlWebpackPlugin({ template: './src/template/index.html', }), new CleanWebpackPlugin(), ], devServer: { contentBase: '../dist', compress: true, port: 8090, open: true, }, } ``` ```javascript // eslintrc.js module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: './tsconfig.json', ecmaFeatures: { jsx: true, }, ecmaVersion: 2018, sourceType: 'module', }, extends: ['plugin:@typescript-eslint/recommended'], plugins: ['@typescript-eslint'], rules: {}, // webstorm老是会去lint这俩文件,然后报错,所以加入忽略 ignorePatterns: ['webpack.config.js', '.eslintrc.js'], } ``` ```bash tsc --init # 初始化tsconfig.json ``` package.json 添加脚本命令 ```json "scripts": { "dev": "webpack-dev-server --mode=development --config ./build/webpack.config.js", "build": "webpack --mode=production --config ./build/webpack.config.js", "lint": "eslint src --ext .js,.ts --fix" }, ``` # 1. 基本类型 - boolean - number(支持二进制`0b1101`,八进制`0o173`,十六进制`0x7b`) - string - 数组: - number[], string[],...... - Array\.... - 联合类型 (string|number)[] - 元组类型 ```typescript let tuple:[string,number,boolean] = ['a',1,false] 长度和元素类型顺序都有要求 ``` - 枚举类型 ```typescript enum Users { CREATOR, // 0 ADMIN, // 1 USER = 3, // 3 可以自行分配 } console.log(Users.CREATOR) console.log(Users[3]) // USER ``` - any 类型 - void 类型 (undefined,在非 strict 模式下可以是 null) ```typescript const consoleText = (txt: string): void => { console.log(txt) // 因为没有返回值,声明返回类型为void } consoleText('123') ``` - undefined - null - never 类型 ```typescript // 返回never的函数必须存在无法达到的终点 function error(message: string): never { throw new Error(message) } // 推断的返回值类型为never function fail() { return error('Something failed') } // 返回never的函数必须存在无法达到的终点 function infiniteLoop(): never { while (true) {} } const neverVal = (): never => { throw new Error('abc') } myStr = neverVal() // never类型可以赋值给任意类型,但是反过来不可以 ``` - ~~object(eslint 不推荐使用)~~ ```typescript function printObj(obj: object): void { console.log(obj) } printObj({ name: 'Samuel', }) ``` ## 类型断言 ```typescript // 类型断言 const getLength = (target: string | number): number => { /* 在这个if里面我们知道target就是string类型而不是number,但是ts编译器不知道 所以, 需要类型断言告诉编译器这里的target是string类型的 */ if ((target).length || (target as string).length === 0) { return (target).length } else { return target.toString().length } } getLength(123) ``` 类型断言有两种写法 - `(<类型>变量)` - `(变量 as 类型)` **React 只能这么写,因为\<\>会被当成标签** # 2 Symbol Symbols 是不可改变且唯一的。主要用法是用来做对象的属性名,这样可以保证属性名独一无二不被 mixin 覆盖 ```typescript // 没有参数的情况 let s1 = Symbol() let s2 = Symbol() s1 === s2 // false // 有参数的情况 let s1 = Symbol('foo') let s2 = Symbol('foo') s1 === s2 // false ``` ## 类型转换 Symbol 不可以和其他类型的值做运算,但是有 toString 方法,且可以转换成布尔 ```typescript let sym = Symbol('My symbol') 'your symbol is ' + sym // TypeError: can't convert symbol to string 1 + sym // TypeError String(sym) // 'Symbol(My symbol)' sym.toString() // 'Symbol(My symbol)' Boolean(sym) // true !sym // false ``` ## 用处 **Symbol 可以用来解决强耦合的某一个具体的字符串或数字** ```javascript function getArea(shape, options) { let area = 0 switch (shape) { case 'Triangle': // 魔术字符串(强耦合) area = 0.5 * options.width * options.height break /* ... more code ... */ } return area } // 可以定义一个对象来指定case类型 const shapeType = { triangle: 'Triangle', } function getArea(shape, options) { let area = 0 switch (shape) { case shapeType.triangle: // 解耦 area = 0.5 * options.width * options.height break } return area } // 其实triangle是什么字符串不重要,因此可以使用Symbol const shapeType = { triangle: Symbol(), } ``` **Symbol 作为属性名,遍历对象的时候,该属性不会出现在`for...in`、`for...of`循环中,也不会被`Object.keys()`、`Object.getOwnPropertyNames()`、`JSON.stringify()`返回。** ```javascript const nameSymbol = Symbol('name') const obj = { [nameSymbol]: 'Samuel', age: 18, } for (const key in obj) { console.log(key) // age } console.log(Object.keys(obj)) // Array["age"] console.log(Object.getOwnPropertyNames(obj)) // Array["age"] ``` 但是可以被`Object.getOwnPropertySymbols()`和`Reflect.ownKeys()`获取 ```javascript console.log(Object.getOwnPropertySymbols(obj)) // Array [ Symbol(name) ] ``` ## **Symbol.for(),Symbol.keyFor()** 有时,我们希望重新使用同一个 Symbol 值,`Symbol.for()`方法可以做到这一点。 ```javascript let s1 = Symbol.for('foo') let s2 = Symbol.for('foo') s1 === s2 // true ``` `Symbol.keyFor()`会返回一个使用`Symbol.for(key)`登记过的 key ```javascript let s1 = Symbol.for('foo') Symbol.keyFor(s1) // "foo" let s2 = Symbol('foo') Symbol.keyFor(s2) // undefined ``` ## 内置 Symbol 值 ### Symbol.hasInstance 对象的`Symbol.hasInstance`属性,指向一个内部方法。当其他对象使用`instanceof`运算符,判断是否为该对象的实例时,会调用这个方法。比如,`foo instanceof Foo`在语言内部,实际调用的是`Foo[Symbol.hasInstance](foo)`。 ```javascript // es6 js, 非ts class MyClass { [Symbol.hasInstance](foo) { console.log(foo) } } ;[1, 2, 3] instanceof new MyClass() ``` ### Symbol.isConcatSpreadable 对象的`Symbol.isConcatSpreadable`属性等于一个布尔值,表示该对象用于`Array.prototype.concat()`时,是否可以展开。 ```javascript let arr1 = ['c', 'd'] ;['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e'] arr1[Symbol.isConcatSpreadable] // undefined let arr2 = ['c', 'd'] arr2[Symbol.isConcatSpreadable] = false ;['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e'] ``` ### Symbol.species 对象的`Symbol.species`属性,指向一个构造函数。创建衍生对象时,会使用该属性。 ```javascript class MyArray extends Array {} const a = new MyArray(1, 2, 3) const b = a.map((x) => x) const c = a.filter((x) => x > 1) b instanceof MyArray // true c instanceof MyArray // true ``` 子类`MyArray`继承了父类`Array`,`a`是`MyArray`的实例,`b`和`c`是`a`的衍生对象。你可能会认为,`b`和`c`都是调用数组方法生成的,所以应该是数组(`Array`的实例),但实际上它们也是`MyArray`的实例。 ```javascript class MyArray extends Array { static get [Symbol.species]() { return Array } } const a = new MyArray() const b = a.map((x) => x) b instanceof MyArray // false b instanceof Array // true ``` 上面代码中,`a.map(x => x)`生成的衍生对象,就不是`MyArray`的实例,而直接就是`Array`的实例。 ### 有关字符串的 3 个 Symbol - Symbol.match - Symbol.replace - Symbol.split ```javascript String.prototype.replace(searchValue, replaceValue) // 等同于 searchValue[Symbol.replace](this, replaceValue) ``` ### Symbol.iterator 对象的`Symbol.iterator`属性,指向该对象的默认遍历器方法。 ```javascript const myIterable = {} myIterable[Symbol.iterator] = function* () { yield 1 yield 2 yield 3 } ;[...myIterable] // [1, 2, 3] myIterable.next() ``` ### Symbol.toPrimitive 当对象转换为基本类型时调用这个方法 ```javascript let obj = { [Symbol.toPrimitive](hint) { switch (hint) { case 'number': return 123 case 'string': return 'str' case 'default': return 'default' default: throw new Error() } }, } 2 * obj // 246 3 + obj // '3default' obj == 'default' // true String(obj) // 'str' ``` ### Symbol.toStringTag 对象的`Symbol.toStringTag`属性,指向一个方法。这个属性可以用来定制 Object.prototype.toString.call(obj)的返回的`[object Object]`或`[object Array]`中`object`后面的那个字符串。 ```javascript class Collection { get [Symbol.toStringTag]() { return 'xxx' } } let x = new Collection() Object.prototype.toString.call(x) // "[object xxx]" ``` # 3. 接口 ## 限制对象属性 使用接口限制对象的属性和属性类型 ```typescript // 使用接口限制属性类型 interface NameInterface { firstName: string lastName: string } // 限制对象作为参数传递时属性的类型 function getFullname({ firstName, lastName }: NameInterface): string { return `${firstName}·${lastName}` } console.log( getFullname({ firstName: 'Samuel', lastName: 'Sue', }) ) ``` ## 可选属性 可选属性的限制 ```typescript // 对于可选参数接口 interface VegetableInterface { color?: string // 可以有color也可以没有 type: string } function VegInfo({ color, type }: VegetableInterface): string { return `${color || ''}${type}` } console.log(VegInfo({ type: 'Onion' })) ``` ## 多余属性 对于多余属性,有三种方法解除属性限制 ```typescript enum Gender { Female, Male, } // 对于多余属性 interface PersonInfo { name: string age: number gender: Gender // 多余属性解决方法1, 定义其他属性 [props: string]: any } function logPerson({ name, age, gender }: PersonInfo): void { console.log(`Person:${name},${age} years old,${gender}`) } logPerson({ name: 'Samuel', age: 25, gender: Gender.Male, hobby: 'Basketball', // 可以传递多余属性 }) // ========================================== logPerson({ name: 'Samuel', age: 25, gender: Gender.Male, hobby: 'Basketball', // 可以传递多余属性 } as PersonInfo) // 多余属性解决方法2,类型断言 // =========================================== // 多余属性解决方法3 类型兼容性 const person = { name: 'Samuel', age: 25, gender: Gender.Male, hobby: 'Basketball', // 可以传递多余属性 } logPerson(person) ``` 注意,剩余属性(属性索引)是规定了其他属性的类型的,举个例子: ```typescript interface Role { [prop: string]: number // 这就规定了Role接口描述的对象的所有属性的值都必须是number // name: string, // 这就报错了,因为name属性的值是string类型的,没法赋值给number类型. } ``` ## 只读属性 ```typescript // 只读属性 interface ArrInterface { 0: number // 0位置只能是number readonly 1: string // 1位置只读,不可改 } const arr: ArrInterface = [123, 'hahah'] // arr[1] = 'good' // Cannot assign to '1' because it is a read-only property. ``` ## 属性类型 ```typescript // 属性名如果是number类型,会自动转换成string interface RoleDict { [id: string]: number } const role: RoleDict = { 12: 123, // 12实际上是转换成了string // '12':234 // 报 属性重复 的错误 } console.log(role['12']) ``` ## 接口继承 ```typescript // 接口继承 interface Tomato extends VegetableInterface { radius: number } ``` ## 函数接口 函数接口: 注意函数类型写法`(arg:string,arg2:number):void`,即(参数:类型...):返回值类型 ```typescript // 计数器函数接口 interface CounterInterface { (): void // 函数类型属性, 参数为空,无返回值 counter: number } const countFn = (): CounterInterface => { const fn = function () { fn.counter++ } fn.counter = 0 return fn } const countFunc: CounterInterface = countFn() countFunc() console.log(countFunc.counter) countFunc() console.log(countFunc.counter) ``` # 4. 函数 ## 函数类型 ```typescript // 接口来定义函数类型(lint推荐使用类型别名来定义) interface AddFnInterface { (x: number, y: number): number // 函数类型属性 } // 类型别名定义函数类型 type Add = (x: number, y: number) => number const addFunc: Add = (x: number, y: number) => x + y ``` ## 可选参数,默认参数 ```typescript // 函数可选参数,默认参数 type AddFnType = (x: number, y?: number) => number const fn2: AddFnType = (x = 3, y) => x + (y || 0) ``` ## 剩余参数 ```typescript // 剩余参数 type SumFnType = (...args: number[]) => number const fn3: SumFnType = (...args) => args.reduce((previousValue, currentValue) => previousValue + currentValue, 0) console.log(fn3(1, 2, 3, 4, 5, 6)) ``` ## 函数重载 ```typescript // 函数重载(ts的重载只是用于代码提示,不是真正意义上的重载) function splitChar(x: number): string[] function splitChar(x: string): string[] function splitChar(x: any): any { // 前两个是方法声明(支持重载类型), 这里才是方法实体(必须这么写) if (typeof x === 'string') { return x.split('') } else { return (x as number).toString().split('') } } console.log(splitChar(123456)) console.log(splitChar('我是爸爸')) ``` # 5. 泛型 对于函数,传入的参数类型事前无法确定,在调用时确定,且返回值类型也需要根据参数类型确定,同时对于这种动态的写法我们有时仍需要对类型做判断以调用不同类型对象的方法。 这时候我们需要的就是**泛型** ## 基本使用 ```typescript // (函数)变量类型泛型 const genArr = (val: T, times: number): T[] => { return new Array(times).fill(val) } // 函数类型别名泛型 type GenArr = (val: T, times: number) => T[] const genFn: GenArr = (val, times) => { return new Array(times).fill(val) } // genArr(123,3).map(item=>item.length) // Property 'length' does not exist on type 'number'. genArr(123, 3).map((item) => item.toFixed()) console.log(genArr('爸爸', 3).map((item) => item.split(''))) // 根据val类型, 可以提示响应类型的方法 ``` ## 泛型约束(结合接口) ```typescript // 泛型约束, 假如只允许带length属性的类型传入 interface WithLength { length: number } const genFn2 = (val: T, times: number): T[] => { return new Array(times).fill(val) } genFn2([1, 2, 3], 2) genFn2('爸爸', 2) // genFn2(123,2) //Argument of type '123' is not assignable to parameter of type 'WithLength ``` ### 理解泛型中的 keyof ```typescript interface WithLength { length: number } // 这里T extends WithLength, 理解上就是泛型类型T=WithLength(type T = WithLength) const genFn2 = (val: T, times: number): T[] => { return new Array(times).fill(val) } interface Person { name: string age: number gender: string } class Teacher { constructor(private info: Person) {} /* keyof Person就是:'name','age','gender', T extends 'name':等价与 type T = 'name' ... T类型就是属性名的联合类型。 所以这里参数key就是属性名。 Person[T]就是属性值类型 */ getInfo(key: T): Person[T] { return this.info[T] } } ``` ## 泛型约束(结合索引类型) ```typescript // 泛型约束, 只允许访问对象存在的属性 function showProp(obj: T, prop: K) { return obj[prop] } const person1 = { name: 'Samuel', age: 26, gender: 'Male', } console.log(showProp(person1, 'age')) // console.log(showProp(person1,"addr")) //Argument of type '"addr"' is not assignable to parameter of type '"age" | "name" | "gender"'. ``` # 6. 类 ## es6 的类 补充知识点: - set/get 关键字操作属性值 - 私有化方法/属性(不是语法上的添加`_`,真正意义上的私有化) - new.target ```javascript class Parent{ constuctor(){ if(new.target===Parent){ throw new Error('父类不允许实例化') } } } let privateSymbol = new Symbol('name') class Child extends Parent{ constructor(name){ super() this[privateSymbol] = name console.log(new.target) } getName(){ return this[privateSymbol] } } export Child // 只导出Child类,不导出privateSymbol,外部无法直接访问 // new Parent // throw Error new Child() // 打印Child的构造方法(也就是这个类) ``` - super 关键字的用法 ```javascript class Parent { constructor() { this.name = 'Parent' } showName() { return this.name } static getName() { return Parent.name } } class Child extends Parent { constructor() { super() // 指向父类构造方法 this.name = 'Child' } showParentName() { return super.showName() // 成员方法中super指向父类实例(但是this是指向自己的) } static getParentName() { return super.getName() // 静态方法中super指向父类静态类 } } Child.getParentName() // 'Parent' new Child().getParentName() // 'Parent' super指向父类实例(但是this是指向自己的) ``` ## TS 中的类 ### 属性修饰符 - public - private(仅自身可用) - protected(仅自身和派生类[子类]可用) ```typescript class Parent { protected x: number // 构造方法声明protect, 只能在派生类中被super执行 protected constructor(x: number) { this.x = x } protected getX() { return this.x } } class Child extends Parent { constructor(x: number) { super(x) // } getX(): number { // console.log(super.x) //子类内部只能通过super访问父类的protect方法,不能访问protect属性 return super.getX() } } // const p = new Parent(123) const ch = new Child(234) console.log(ch.getX()) ``` ### 只读属性 ```typescript // 只读属性 class ReadOnlyInfo { public readonly info: string constructor(info: string) { this.info = info } } const rInfo = new ReadOnlyInfo('只能读,不能改') // rInfo.info = '改一个试试' // Cannot assign to 'info' because it is a read-only property. console.log(rInfo) ``` ### 参数修饰符 ```typescript // 参数修饰符(简写) class A { constructor(private name: string) { // 使用参数修饰符可以自动帮你把参数绑定到this上 } } const a = new A('Samuel') console.log(a) // {name:'Samuel'} ``` ### 静态成员属性 ```typescript // 静态成员属性 class B { public static readonly pi = 3.1415926 private static num = 2333 } console.log(B.pi) // B.pi = 3.14 // console.log(B.num) ``` ### 可选属性/参数, 存取器 ```typescript // 可选属性/可选参数 set/get class Human { private _name: string public age?: number constructor(name: string, age?: number, public gender?: string) { this._name = name this.age = age } public set name(newVal: string) { this._name = newVal } public get name() { return this._name.replace(/./, (substring) => substring.toUpperCase()) } } const h = new Human('samuel') h.name = 'zhangboy' console.log(h.name) ``` ### 抽象类 ```typescript // 抽象类 abstract class MyFile { protected constructor(public size: number) {} public abstract save(): void // 抽象函数,没有函数体 } class MediaFile extends MyFile { constructor(public size: number, public type: string) { super(size) // 必须执行父类(抽象类)构造函数 } save(): void { console.log('save media file') } play(): void { console.log('play media File') } } ``` ### 类实现接口 ```typescript // 类实现接口 interface player { supportType: string[] play: () => void } class MusicPlayer implements player { play(): void { console.log('play music') } supportType: string[] constructor(...types: string[]) { this.supportType = types } } const player = new MusicPlayer('mp3', 'wav', 'wma', 'acc') player.play() ``` ### 接口继承类 ```typescript // 接口继承类 class Point { protected x: number // 因为是protect,因此只能在内部或者派生类中初始化 protected y: number protected constructor(x: number, y: number) { this.x = x this.y = y } } interface Point3d extends Point { z: number } // 因为要初始化protected x,y 所以要继承类 class AcPoint extends Point implements Point3d { z: number constructor(x: number, y: number, z: number) { super(x, y) this.z = 312 } } ``` ### 类类型 ```typescript /* 假如我们要限制参数是一个具体类的构造函数,此时就需要使用类类型 */ class TestE { public info: string constructor() { console.log('constructor exec') this.info = 'hhh' } } // c的类型是对象类型且这个对象包含返回类型是T的构造函数。 // 理解c是一个对象(构造函数),执行new之后返回一个类型为T的对象 const testFn1 = (c: new () => T): T => { return new c() } testFn1(TestE) ``` # 7. 枚举 ## 枚举值编号 ```typescript const f = 400 enum Code { OK, Fault = f, // 可以用const或者函数返回值来编号 Error = 3, // 紧接着的一个枚举值要手动编号 Success, } console.log(Code[400]) // 可以通过编号获取枚举值 ``` ## 字符串枚举 ```typescript // 字符串枚举 enum reply { Error = 'Sorry, an error has occurred', Success = 'No problem', Failed = Error, // 可以引用枚举 } ``` ## ~~异构枚举~~(不推荐) ```typescript // 异构枚举, 枚举值类型不同(不推荐) enum XX { a = 1, b = 'hehe', } ``` ## 枚举成员类型和联合枚举类型 如果一个枚举里所有成员的值都是字面量类型的值,那么这个枚举的每个成员和枚举本身都可以作为类型来使用。字面量枚举成员需满足以下条件: - 不带初始值的枚举成员,例如 enum E { A } - 值为字符串字面量,例如 enum E { A = 'hello' } - 值为数字字面量,或者带有一元 `- `符号的数字字面量,例如 enum E { A = 1 },enum E { A = -1 } ```typescript // 枚举成员类型 enum ShapeKind { Circle, Square, } interface Circle { kind: ShapeKind.Circle // 使用 ShapeKind.Circle 作为类型,指定接口须有 kind 字段,且类型为 ShapeKind.Circle radius: number } let c: Circle = { kind: ShapeKind.Square, // Error! 因为接口 Circle 的 kind 被指定为 ShapeKind.Circle类型,所以这里会报错 radius: 100, } // 联合枚举 enum Status { on, off, } interface Light { status: Status // 只能是Status中的枚举值 } const light: Light = { status: Status.off, } ``` ## const 枚举 普通的枚举在编译后会实实在在创建一个对象(枚举名同名),然后让变量指向对象的值(枚举值),使用 const 枚举后不会创建这个对象,而是直接用 ```typescript // const enum const enum Animal { Dog, Cat, GoldFish, } const kitty = Animal.Cat // 编译后是kitty=1, 不会产生Animal对象 ``` # 8. 类型推断和类型兼容 ## 类型推断 - ts 会自动推断写的代码是什么类型 - 当你不需要 ts 自动推断类型的时候 需要用到类型断言 - 最佳通过类型推断 - 上下文类型推断 ```typescript // 断言为number let a = 1 // 断言为number[] let b = [1, 2] ``` ## 类型兼容 - 当一个类型 Y 可以被赋值给另外一个类型 X 时,我们就可以说类型 X 兼容类型 Y - 结构之间兼容,成员少的兼容成员多的 - 函数之间兼容,参数多的兼容参数少的 ```typescript // 类型兼容 let fn1 = (x: string, y: string): void => { console.log(11) } let fn12 = (x: string): void => { console.log(22) } // fn12 = fn1 // 函数,只能参数多的兼容少的 fn1 = fn12 interface Aitf { name: string } interface Bitf { name: string age: number } let aObj: Aitf = { name: 'Samuel' } let bObj: Bitf = { name: 'Samuel', age: 25 } // bObj = aObj // 结构数据,只能成员少的兼容多的 aObj = bObj ``` - 函数重载类型兼容 ```typescript // 重载类型兼容 function shk(x: number, y: number): number function shk(x: string, y: string): string function shk(x: any, y: any): any { return x + y } function shl(x: number, y: number): number function shl(x: any, y: any): any { return x + y } let fna = shl fna = shk // 重载类型少的兼容多的 ``` ```typescript // 类兼容性 class AC { // public static num = 3.14 // 静态成员不参与兼容性检测 protected num: number private age: number constructor(public name: string, age: number) { this.num = 13 this.age = age } } class ACsub extends AC { constructor(public name: string, age: number) { super(name, age) this.num = 15 } } class BC { constructor(public name: string) {} } let ax: BC = new BC('name') // 结构成员 ax = new AC('hh', 17) // 少的兼容多的 ax = new ACsub('hh', 25) // protected以及private成员必须保证来自同一个类 console.log(ax) ``` # 9. 高级类型 ## 交叉类型 交叉类型是将多个类型合并为一个类型。 可以理解成**逻辑与** ```typescript const mergeObj = (a: T, b: U): T & U => { let res = {} as T & U // 类型断言 res = Object.assign(a, b) return res } ``` ## 联合类型 可以理解成**或** ```typescript const showRes = (): string | number => { const arr = ['Samuel', 123] return Math.random() > 0.5 ? arr[0] : arr[1] } ``` 注意一点,举个例子: ```typescript interface Bird { fly(): void layEggs(): void } interface Turtle { swim(): void layEggs(): void } function getSmallPet(): Bird | Turtle { return {} as Bird | Turtle } getSmallPet().layEggs() // 返回的联合类型只能访问其类型的共有属性 // getSmallPet().swim() // 各自的非共有属性无法访问 ``` ## 类型保护 如果我们想确定变量的类型,比如是不是 string 类型或者其他类型,以往我们需要做大量的类型断言,ts 提供类型保护机制,可以方便使用 ```typescript function isString(x: string | number): x is string { return typeof x === 'string' } const res1 = showRes() // 联合类型 string | number if (isString(res1)) { console.log('string length:', res1.length) } else { console.log('number:', res1.toFixed()) } ``` **typeof 做简单的类型保护** typeof 能判断的类型有:string/number/boolean/symbol/undefined/function,其他都是 object ts 中用 typeof 做类型保护只能判断 string/number/boolean/symbol,其他类型不会识别为类型保护 ```typescript // 对于简单的类型保护,可以直接使用typeof(只能做===或!==判断) if (typeof res1 === 'string') { console.log('string length:', res1.length) } else { console.log('number:', res1.toFixed()) } ``` **intanceof 做类型保护** 判断是否是特定类创建的对象 ```typescript class Axx { public age = 13 } class Bxx { public name = 'Sam' } function getRandomAB() { return Math.random() > 0.5 ? new Axx() : new Bxx() } const rsAB = getRandomAB() if (rsAB instanceof Axx) { console.log(rsAB.age) } else { console.log(rsAB.name) } ``` **类型保护之 in** ```typescript interface User { name: string age: number occupation: string } interface Admin { name: string age: number role: string } export type Person = User | Admin export const persons: Person[] = [ { name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep', }, { name: 'Jane Doe', age: 32, role: 'Administrator', }, { name: 'Kate Müller', age: 23, occupation: 'Astronaut', }, { name: 'Bruce Willis', age: 64, role: 'World saver', }, ] export function logPerson(person: Person) { let additionalInformation: string // 通过in即可完成类型保护 if ('role' in person) { additionalInformation = person.role } else { additionalInformation = person.occupation } console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`) } persons.forEach(logPerson) ``` ## null/undefined 类型断言 ```typescript function getPrefixStr(x: number | null) { function addPrefix(prefix: string): string { // 因为下面已经对null情况进行排除,这里使用'!'类型断言x不为null/undefined return prefix + x!.toFixed().toString() } x = x || 0.1 return addPrefix('pr') } ``` ## 类型别名 类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。 ```typescript type Container = { value: T } type Tree = { value: T left: Tree right: Tree } type LinkedList = T & { next: LinkedList } interface Person { name: string } var people: LinkedList var s = people.name var s = people.next.name var s = people.next.next.name var s = people.next.next.next.name ``` type 可以给抽象类取别名,但是注意别名不能使用 extends,implement ```typescript abstract class AbsC { protected name: string protected constructor() { this.name = 'Samuel' } } type absc = AbsC // 不能Extends class CCC extends AbsC { constructor() { super() } } ``` ## 字面量类型 ```typescript // 字符串字面量类型 type Direction = 'east' | 'west' | 'south' | 'north' const DirFn = (direc: Direction) => { console.log(direc) } DirFn('west') // DirFn('hollyshit') // Argument of type '"hollyshit"' is not assignable to parameter of type 'Direction'. // 同理还有数字字面量类型 ``` ## 可辨识联合 1. 具有普通的单例类型属性 —_可辨识的特征_。 2. 一个类型别名包含了那些类型的联合—_联合_。 3. 此属性上的类型保护。 ```typescript interface Square { kind: 'square' size: number } interface Rectangle { kind: 'rectangle' width: number height: number } interface Circle { kind: 'circle' radius: number } type Shape = Square | Rectangle | Circle // 声明为联合类型 function area(s: Shape) { // 类型保护 switch (s.kind) { case 'square': return s.size * s.size case 'rectangle': return s.height * s.width case 'circle': return Math.PI * s.radius ** 2 } } ``` ## 索引类型 **keyof** 返回索引属性名构成的联合类型 ```typescript interface ACtp2 { name: string age: number } let k: keyof ACtp2 // 返回索引属性名构成的联合类型 "name" | "age" ``` **索引访问操作符** ```typescript // --------------------------------------------------- T[K]是索引访问操作符 function pluck(o: T, names: K[]): T[K][] { return names.map((n) => o[n]) } interface Person { name: string age: number } let person: Person = { name: 'Jarid', age: 35, } let strings: string[] = pluck(person, ['name']) // ok, string[] ``` 在普通的上下文里使用`T[K]`需要保证`K extends keyof T` 使用`[]`意味着取出属性(值或类型) ```typescript // [] 取出接口类型 interface Types { a: number b: string c: boolean } type AllTypes = Types[keyof Types] // number | string | boolean ``` ## 映射类型 简单例子:将一个类型的所有属性都变成可读 ```typescript type ReadOnlyTypes = { readonly [P in keyof T]: T[P] // in 表示遍历 } ``` 内置的映射类型: - Readonly 只读 - Partial 可选 - Pick T 里面保留 K(可以是联合类型)属性 - Record 创建一个类型,K 代表键值的类型, T 代表值的类型 ```typescript // Record实例 将属性值映射为属性string的长度 function record( obj: Record, fn: (x: T) => U ): Record { const res: any = {} for (const key in obj) { res[key] = fn(obj[key]) } return res } console.log(record(person2, (p) => p.toString().length)) ``` - Omit 实现排除已选的**属性** ```typescript type User = { id: string name: string email: string } type UserWithoutEmail = Omit // alias for '{id:string,name:string}' ``` - `Exclude` 从 T 中提出可以赋值给 U 的**类型** - `Extract` -- 提取`T`中可以赋值给`U`的类型。 - `NonNullable` -- 从`T`中剔除`null`和`undefined`。 - `ReturnType` -- 获取函数返回值类型。 - `InstanceType` -- 获取构造函数类型的实例类型。 可以显式的指定添加或着删除修饰符 ```typescript // 显示添加/删除修饰符 type RemoveReadonly = { -readonly [P in keyof K]: K[P] // - 表示删除修饰符,可以是readonly,?(可选修饰符) } type noReadOnlyTypes = RemoveReadonly ``` ## unknown 类型 1. 任何类型都可以赋值给 unknown 类型 2. 在没有类型断言或者基于控制流的类型细化时,unknown 不可以赋值给其他类型 3. 在没有类型断言或者基于控制流的类型细化时,不可以对 unknown 类型对象进行任何操作 ```typescript let val2: unknown // val2++ // Object is of type 'unknown'. ``` 4. unknown 与其他类型组成交叉类型, 就等于其他类型 5. unknown 与其他类型组成联合类型, 等于 unknown 6. never 是 unknown 的子类型 7. keyof unknown 得到 never 8. unknown 类型只能进行===或!==操作, 不能进行其他操作 9. unknown 类型不能访问属性,作为函数调用以及作为类创建对象 10. 使用类型映射时如果映射的属性是 unknown 类型,就不会产生映射 ```typescript type NumsType = { [P in keyof T]: number } type xxd = NumsType // Initial type:{[p: string]: number} type xxs = NumsType // Initial type:{} ``` ## 条件类型 **extends 条件类型** ```typescript type Typs = T extends string ? string : number let vals2: Typs<'2'> // let vals2: string let vals3: Typs // let vals3: number 走后面的逻辑 ``` **分布式条件类型** ```typescript type onlyString = T extends string ? T : never type someTypes = 'a' | 'b' | 123 | false type resultTypes = onlyString // Initial type: "a" | "b" ``` **条件类型加索引类型** ```typescript type reserveFunction = { [P in keyof T]: T[P] extends () => void ? P : never // 取出所有是函数类型的属性P, 其他为never }[keyof T] // 索引属性来去掉never类型属性 /* 不加这个索引属性,下面的Res2会变成: type Res2 = { user: never; code: never; subparts: never; updateTime: "updateTime"; thankU: "thankU"; } */ interface Part { user: 'Sam' code: 132 subparts: Part[] updateTime: () => void thankU(): void } type Res2 = reserveFunction const obj2: Res2 = 'updateTime' ``` **内置条件类型** - Exclude 从 T 里面排除 U ```typescript type typesA = Exclude<'a' | 'b' | 'c', 'a' | 'b'> // "c" ``` - Extract 从 T 中选取可以赋值给 U 的类型 ```typescript type typesB = Extract<'a' | 'b' | 'c', 'a' | 'b'> // "a" | "b" ``` - NonNullable\ 去掉 null 类型(undefined,null) ```typescript type typesC = NonNullable<'a' | 'b' | 'c' | null | undefined> // Initial type:"a" | "b" | "c" ``` - ReturnType\ T 的返回值类型 ```typescript type typesD = ReturnType<() => string> //Initial type:string ``` - InstanceType\ ts 中类有 2 种类型, 静态部分的类型和实例的类型, 所以 T 如果是构造函数类型, 那么 InstanceType 可以返回他的实例类型 ```typescript class AClass { constructor() { console.log('AClass') } } interface AConstructor { new (): string } type typeE = InstanceType // AClass type typeF = InstanceType // string ``` - Parameters\ 获取函数参数类型 ```typescript interface A { (a: number, b: string): string[] } type A1 = Parameters // [number, string] ``` - ConstructorParameters 获取构造函数的参数类型 ```typescript class AClass { constructor(public name: string) { console.log('AClass') } } type typeG = ConstructorParameters // string ``` ## 推断元素类型 ```typescript // 推断元素类型 type Type2 = T extends any[] ? T[number] : T // 如果T是一个数组, 那就取T元素的类型(当元素类型不一样时是联合类型) type typesX = Type2 // Initial type:string type typesX2 = Type2 // Initial type:boolean // infer 推断元素类型 type Type3 = T extends Array ? U : T type typesY = Type3 // Initial type:string type typesY2 = Type3 // Initial type:boolean type typesY3 = Type3<[false, 123, 'sam']> // Initial type:false | 123 | "sam" ``` ```typescript // T是一个函数类型(参数:any)返回值any, infer P推断参数类型 type Parameters any> = T extends ( ...args: infer P ) => any ? P : never // T是一个构造函数类型 type ConstructorParameters any> = T extends new ( ...arg: infer P ) => any ? P : never ``` # 10. 命名空间 为了防止全局变量过多,使用命名空间来管理 ```typescript // components.ts namespace Components { // 导出需要暴露的内容 export class Header { constructor() { const elem = document.createElment('div') elem.innerText = 'this is header' document.body.appendChild(elem) } } export class Content {} export class Footer {} // 可以暴露接口 export interface User { name: string } // 可以暴露命名空间 export namespace Sub { export class Test {} } } ``` ```typescript /// /* 有一个组件命名空间,还有页面的命名空间 page.ts */ namespace Home { export class Page { constructor() { new Components.Header() new Components.Content() new Components.Footer() } } } ``` ~~这个不太好用,好像没有挺多应用。~~ **在给第三方库的描述文件中有很多引用。** # 11. 描述文件 **针对全局的描述文件** ```typescript // jquery.d.ts // 声明全局变量 declare var $: (param: () => void) => void // 定义接口 interface JqueryInstance { html: (html: string) => JqueryInstance } // 声明全局函数 declare function $(readyFunc: () => void): void declare function $(selector: string): JqueryInstance // 声明一个对象(有属性嵌套, new $.fn.init()) declare namespace $ { // 用namespace来 namespace fn { class init {} } } ``` **针对模块的描述文件** ```typescript // jquery.d.ts // 声明一个模块 declare module 'jquery' { interface JqueryInstance { html: (html: string) => JqueryInstance } declare function $(readyFunc: () => void): void declare function $(selector: string): JqueryInstance // 最终要导出出去 export = $ } ``` # 12. 装饰器 装饰器本身就是一个函数 ## 类装饰器 类装饰器接收的参数是**构造函数** ```typescript function testDecorator(constructor: new (...args: any[]) => any) { constructor.prototype.getName = () => { console.log('dell') } console.log('decorator') } function testDecorator1(constructor: any) { console.log('decorator1') } @testDecorator1 @testDecorator class Test {} ;(new Test() as any).getName() ``` 装饰器如果要支持接收参数,那么可以用闭包做装饰器 ```typescript // 闭包作装饰器 function decor1(flag: boolean) { if (flag) { return function (constructor: any) { // 修改构造函数的原型(类的原型) constructor.prototype.info = () => { console.log('dell') } } } return function (constructor: any) {} } @decor1(true) // 接收参数 class Test1 {} // 但是这么写,ts无法检测到原型上的info函数,因此无法推断出Test1类型的对象上面有info函数 ``` **如果类装饰器返回一个值,他会使用提供的构造函数来替换类的声明。** ```typescript // 类装饰器泛型 function testDecor any>(constructor: T) { return class extends constructor { name = 'lee' getName() { console.log(this.name) } } } @testDecor class Person { name: string constructor(name: string) { this.name = name } } ;(new Person('sam') as any).getName() // 这种写法TS识别不到getName(), 改进方式是以下写法 ``` 装饰器工厂函数写法 ```typescript // 装饰器工厂函数写法 function decoratorFactory() { return function any>(constructor: T) { return class extends constructor { name = 'lee' getName() { console.log(this.name) } } } } // 利用工厂函数对匿名类进行装饰 const Person1 = decoratorFactory()( class { name: string constructor(name: string) { this.name = name } } ) new Person1('sam').getName() // 拓展的方法可以显示出来 ``` ## 方法装饰器 ```typescript // 方法装饰器 function funcDecor(target: any, key: string, descriptor: PropertyDescriptor) { /* target参数是方法对应的那个类的prototype 如果方法是静态方法,target是那个类的构造函数 */ console.log(target) console.log(key) // key是被装饰的方法名 console.log(descriptor) // js的属性描述 // descriptor.writable = false // 直接修改方法 const oldVal = descriptor.value as (...args: any[]) => any descriptor.value = function (...args: any[]) { return oldVal.apply(this, args) + 'NO!!!!!!!!!!!' } } class UObj { name: string constructor(name: string) { this.name = name } // 方法装饰器 @funcDecor getName() { return this.name } } const objU = new UObj('sam') console.log(objU.getName()) ``` ## 访问器装饰器 注意: set 和 get 不能同时绑定,目前用处不知道。 ```typescript function visitDecor(target: any, key: string, descriptor: PropertyDescriptor) { console.log('descriptor:', descriptor) } class XObj { private _name: string constructor(name: string) { this._name = name } @visitDecor get name() { return this._name } // @visitDecor set name(name: string) { this._name = name } } ``` 访问器装饰器和方法装饰器区别: 1. 访问器装饰器的 descriptor 有 set,get, 没有 value 和 writable 2. 方法装饰器没有 set/get,有 value 和 writable ## 属性装饰器 属性装饰器不接受 descriptor 参数,但是可以返回一个新的 descriptor 来覆盖 ```typescript // 属性装饰器 function propDecorator(target: any, key: string): any { // 创建新的属性描述符来覆盖(返回) /* const descriptor:PropertyDescriptor = { writable: false } return descriptor */ // 因为target是类对应原型,因此修改是原型上的属性,而不是实例上的属性 target[key] = 'NoNoNO' } class XaObj { @propDecorator name = 'Dell' } const xao = new XaObj() xao.name = 'Samuel' console.log(xao.name) // Samuel console.log((xao as any).__proto__.name) // NoNoNO ``` ## 参数装饰器 ```typescript /** * * @param target 原型 * @param method 方法名 * @param paramIndex 参数的位置索引 */ function paramDecorator(target: any, method: string, paramIndex: number) { console.log(target, method, paramIndex) } class Sth { getInfo(name: string, @paramDecorator age: number) { console.log(name, age) } } new Sth().getInfo('Sam', 30) ``` ## 执行顺序 ```typescript 属性》方法》方法参数》类 ``` ## 结合 Reflect-metadata 元数据可以理解为在任意对象上挂载了一个 Map,用于绑定键值信息 ```typescript import 'reflect-metadata' function Role(name: string): ClassDecorator { // 类装饰器, target是构造函数 return (target) => { Reflect.defineMetadata('role', name, target.prototype) console.log(target) } } @Role('admin') class Post {} const metaData = Reflect.getMetadata('role', Post.prototype) console.log(metaData) const np = new Post() const anotherMd = Reflect.getMetadata('role', (np as any).__proto__) console.log(anotherMd) ```