基本写法对照表
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.Up
let down = Direction.Down
let left = Direction.Left
let right = Direction.Right
console.log(up, down, left, right)
// -> 0, 1, 2, 3
enum Direction { Up = 1, Down, Left, Right }
let up = Direction.Up
let down = Direction.Down
let left = Direction.Left
let right = Direction.Right
console.log(up, down, left, right)
// -> 1, 2, 3, 4
enum Direction { Up = 8, Down = 10, Left = 12, Right = 14 }
let up = Direction.Up
let down = Direction.Down
let left = Direction.Left
let right = Direction.Right
console.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.name
this.age = aniInfo.age
this.gender = aniInfo.gender
}
// 方法
getName (): string {
return this.name
}
getAge (): number {
return this.age
}
}
// 继承了父类的方法和属性
class Dog extends Animal {
color: string
constructor (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: string
constructor (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 {
@log
add (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
。