基本写法对照表
| Typescript | Javascript(ES6) | 说明 |
|---|---|---|
| let isDone:boolean = true | let isDone = true | 定义布尔类型 |
| let str:string = ‘abc’ | let str = ‘abc’ | 定义字符串类型 |
| let num:number = 10 | let num = 10 | 定义数字类型 |
| let arr:number[] = [1, 2, 3] let arrStr:string[] = [‘a’, ‘b’, ‘c’] let arrX:Array |
let arr = [1, 2, 3] let arrStr = [‘a’, ‘b’, ‘c’] let arrX = [‘a’, ‘b’, ‘c’] |
定义数组类型 |
| let peopleInfo:[string, number] = [‘male’, 23] | — | 定义元组类型(Tuple) |
| enum Direction { Up = 1, Down, Left, Right } | — | 定义枚举类型(enum) |
| let s:any = 4 | — | 定义任意类型 |
| let unusable:void = undefined | — | |
| let u:undefined = undefined let n:null = null |
let u = undefined let n = null |
|
| never | — | never类型表示的是那些永不存在的值的类型 |
关于枚举类型的补充
enum Direction { Up, Down, Left, Right }let up = Direction.Uplet down = Direction.Downlet left = Direction.Leftlet right = Direction.Rightconsole.log(up, down, left, right)// -> 0, 1, 2, 3enum Direction { Up = 1, Down, Left, Right }let up = Direction.Uplet down = Direction.Downlet left = Direction.Leftlet right = Direction.Rightconsole.log(up, down, left, right)// -> 1, 2, 3, 4enum Direction { Up = 8, Down = 10, Left = 12, Right = 14 }let up = Direction.Uplet down = Direction.Downlet left = Direction.Leftlet right = Direction.Rightconsole.log(up, down, left, right)// -> 8, 10, 12, 14
关于void和never的补充
// 没有返回值 返回个空function warnUser():void {console.log("This is my warning message")}// 没有返回值,永远到不了function error(message: string):never {throw new Error(message)}
关于函数的基本用法
interface Person {name: string,age?: number // 可选属性}function getPersonName (person: Person): string {return person.name}const pName = getPersonName({ name: 'zhangsan', age: 20 })console.log(pName)const qName = getPersonName({ name: 'lisi' })console.log(qName)// 可选形参,无返回,且有以下三种写法function add1 (x: number, y?: number): void {console.log(x, y)}const add2 = function (x: number, y?: number): void {console.log(x, y)}const add3 = (x: number, y?: number): void => {console.log(x, y)}
总而言之,就是要明确的知道你传入的是什么,返回的是什么。不是心里知道,而是要通过代码体现出来。一般JS的规范就是要求你心里知道,记不住的写注释,TS从代码上体现出来,同时配合vscode这样的IDE和eslint语法检查,确实能很清楚知道输入的是什么,返回的是什么。
程序是写给人读的,只是偶尔让计算机执行一下。— Donald Knuth
关于类的基本用法
interface AnimalInfo {name: string,age: number,gender: number,color?: string}// 父类class Animal {// 静态方法static getTen (): number {return 10}// 属性// 公开属性public name: string// 受保护权限protected gender: number// 私有属性private age: number// 构造函数constructor (aniInfo: AnimalInfo) {this.name = aniInfo.namethis.age = aniInfo.agethis.gender = aniInfo.gender}// 方法getName (): string {return this.name}getAge (): number {return this.age}}// 继承了父类的方法和属性class Dog extends Animal {color: stringconstructor (animaInfo: AnimalInfo) {super(animaInfo)this.color = animaInfo.color || ''}getColor (): string {return `${this.name}的颜色是: ${this.color}`}getGender (): number {return this.gender}}const jinMao = new Dog({name: '金毛',age: 2,gender: 1,color: '黄色'})// 调用父类方法console.log(jinMao.getName()) // -> 金毛// 显示属性console.log(jinMao.getColor()) // -> 金毛的颜色是: 黄色// 调用静态方法console.log(Animal.getTen()) // -> 10// 调用受保护的属性console.log(jinMao.getGender()) // -> 1// 调用私有属性console.log(jinMao.getAge()) // -> 2// 抽象类abstract class Book {name: stringconstructor (name: string) {this.name = name}getBookName (): void {console.log('Book name: ' + this.name)}abstract printBook(): void // 必须在派生类中实现}class JSBook extends Book {printBook (): void{console.log('This book is printing.')}}let book = new JSBook('Javascript')book.getBookName()book.printBook()
Interface的说明
interface Person {name: string,age?: number, // 可选属性readonly code: string // 只读 定义变量使用const,而属性使用readonly}const createPerson = (info: Person): string => {console.log(info.name)return 'creaeted'}const result = createPerson({name: 'zhangsan',age: 10,code: '430x'})console.log(result)// 类与接口// 门是一个类,防盗门是门的子类。如果防盗门有一个报警器的功能,我们可以简单的给防盗门添加一个报警方法。这时候如果有另一个类,车,也有报警器的功能,就可以考虑把报警器提取出来,作为一个接口,防盗门和车都去实现它:interface Alarm {alert: () => void;}class Door {}class SecurityDoor extends Door implements Alarm {alert () {console.log('SecurityDoor alert');}}class Car implements Alarm { // 实现多个接口 class Car implements Alarm, Light {alert () {console.log('Car alert');}}
声明文件
声明文件必需以 .d.ts 为后缀。其实这个文件对于业务没有什么用,但是对TS有用。举个例子,项目中使用了第三方库,而这个第三方库没有ts版本。ts在编译过程中是不知道这个库是什么东西,所以需要显示的告诉ts,我知道我用的是什么,也知道自己在做什么。
现在大部分库都有自己的声明文件,在安装项目依赖,如 @types/koa ,就是在安装koa的声明文件。
大约是这么理解,有出入以后再补充。
理论上用.d.ts文件,是先需要有代码实现,然后才有声明文件,而且声明文件是为没有ts实现的js库而准备的。也可以说是为了欺骗编译器准备。告诉编译器我知道我在做什么,实际上只有天知道。
泛型
在定义接口、函数、类等的时候,无法确定其参数类型,等到调用的时候才能确定,这种场景就需要用到泛型。
场景1:传递进入什么类型返回什么类型
function returnAny<T> (arg: T): T {return arg;}const a = returnAny<number>(1);const b = returnAny<string>('aa');console.log(a, b); // -> 1, aa
场景2:传入一个数组,将其中互换位置
function swap<T, U> (tup: [T, U]): [U, T] {return [tup[1], tup[0]];}const c = swap(['a', 1]);console.log(c); // -> [1, 'a']
场景3:动态输出object的value
const d = {method: '123',path: 'ss',action: (method: string, path: string): void => {console.log(method, path)}};Object.keys(d).forEach(key => {console.log(d[key])})
上面这段写法移植到TS会报错:
- 元素隐式具有 “any” 类型,因为类型为 “string” 的表达式不能用于索引类型 “{ method: string; path: string; action: (method: string, path: string) => void; }”。
- 在类型 “{ method: string; path: string; action: (method: string, path: string) => void; }” 上找不到具有类型为 “string” 的参数的索引签名。
用泛型可以解决,与场景一有点类似,但是更具体一些
const getProperty = <T, K extends keyof T>(o: T, name: K): T[K] => {return o[name];}Object.keys(d).forEach(key => {console.log(getProperty(d, key))})
泛型约束
interface withLength {length: number}function Logger<T extends withLength> (arg:T): T {console.log(arg.length);return arg;}Logger('abc');Logger(['a', 'b']);Logger({length: 12});// Logger(23); // 没有length不能传递进入
装饰器
使用装饰器需要 tsconfig.json 配置 experimentalDecorators 为true。
装饰器是对类、函数、属性之类的一种装饰,可以针对其添加一些额外的行为。通俗的理解可以认为就是在原有代码外层包装了一层处理逻辑。即使拿掉了也不影响原有的功能。
应用场景举例:
- 打logger
- 验证数据的正确性
简单的测试使用:
function log (target: any, name: string, descriptor: any) {console.log(target, name, descriptor)const _value = descriptor.value;// 改写方法 这里基本就可以做任何你想做的事情descriptor.value = function (x: number, y: number): number {console.log(`算式:${x} + ${y} = ${_value.apply(this, arguments)}`);return _value.apply(this, arguments)}return descriptor}class Foo {@logadd (x: number, y: number): number {return x + y}}let x = new Foo()let y = x.add(1, 2)console.log(y) // -> 算式:1 + 2 = 3 -> 3
上面的例子有个问题,装饰器没有办法传参。可以使用装饰器工厂。
function configurable (value: boolean) {console.log(1, value)return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {descriptor.configurable = value;console.log(2, value)};}class Foo {@configurable(true)add (x: number, y: number): number {return x + y}}
reflect-metadata
Reflect Metadata 是 ES7 的一个提案,它主要用来在声明的时候添加和读取元数据。关联 tsconfig.json 里配置 emitDecoratorMetadata 选项为 true 。安装npm i reflect-metadata --save
要弄清这个需要弄清ES6的 Reflect 和 Proxy。
