TypeScript 开发实战笔记
开篇讲解
掌握类型编程思维
三个要点:
- 类型检查:编译代码时进行严格的类型检查,意味着编码阶段可以发现存在的隐患;
- 语言扩展:ES6+,异步、装饰器、接口、抽象类;
- 工具属性:只是一个工具,任何可以运行 JavaScript 的终端上,无需额外的开销,想一个工具一样;
思维方式决定了编程习惯,编程习惯奠定了工程质量,工程质量划定了能力边界
基础篇
语言类型
强类型语言定义:
强类型语言:不允许改变变量的数据类型,除非进行强制类型转换
弱类型语言:变量可以被赋予不同的数据类型
静态类型语言:在编译阶段确定所有变量的类型
动态类型语言:在执行阶段确定所有变量的类型
强类型语言:不允许程序在发生错误后继续执行。争议:C/C++ 是强类型还是弱类型?
属于弱类型语言,没有对数组越界进行检查,会导致程序崩溃
新建一个 tsc 文件配置命令:tsc --init
生成:tsconfig.json
文件
基础类型
类型注解:
- 作用:相当于强类型语言中的类型声明
- 语法:(变量/函数): type
undefined
不是保留字,全局可以修改重新定义这个变量值,一般通过 void 0
确保返回是 undefined
void
在数组中表示么有任何返回值的类型
never
表示永远不会有返回值,比如异常、死循环
枚举
角色判断
TS 枚举类型解决 枚举:一组有名字的常量集合,分为:常量枚举、字符串枚举、异构枚举 原理:一个对象,反向映射(常量枚举才可以) 枚举成员的值是只读不能修改
枚举成员值的定义:
enum Char {
a,
b = Char.a,
c = 1 + 3,
d = Math.random(),
e = '123'.length
}
enum TestEnum {
A = 'a',
B = 'b',
C = 'c',
}
// type TestTypeKey = 'A' | 'B' | 'C'
type TestTypeKey = keyof typeof TestEnum
// type TestTypeValue = 'a' | 'b' | 'c'
type TestTypeValue = keyof Record<TestEnum, string>
接口类型
变量可以传入多余字段 鸭式变形法:一只鸟看起来像鸭子,叫起来像鸭子,就可以被看成是鸭子
如果 render(变量值),会进行类型检查:
- 类型断言
- 索引签名:字符串索引签名和数字索引签名
interface Names {
[x: string]: string
}
type 和 interface 多数情况下有相同的功能,就是定义类型。但有一些小区别:
- type:不是创建新的类型,只是为一个给定的类型起一个名字。type 还可以进行联合、交叉等操作,引用起来更简洁。
- interface:创建新的类型,接口之间还可以继承、声明合并。如果可能,建议优先使用 interface。
混合接口一般是为第三方类库写声明文件时会用到,很多类库名称可以直接当函数调用,也可以有些属性和方法。例子你可以看一下@types/jest/index.d.ts 里面有一些混合接口。
用混合接口声明函数和用接口声明类的区别是,接口不能声明类的构造函数(既不带名称的函数),但混合接口可以,其他都一样。
函数重载
进一步匹配约束类型
// 前两条声明是重载,目的是将参数类型约束为 number 或 string;最后的实现不是重载,要遵循前面的声明,比如传 boolean 就不可以了。
function add (...rest: string[]): string
function add (...rest: number[]): number
function add (...rest: any[]): any {
const first = rest[0];
if (typeof first === 'string') {
return rest.join(',')
}
if (typeof first === 'number') {
return rest.reduce((pre, cur) => cur + pre)
}
}
console.log(add(1, 2, 2))
console.log(add('1', '2', '2'))
类特性
- 成员属性都是实例属性而不是原型属性;
- 成员方法都是原型方法而不是实例方法;
class Dog {
constructor(name: string) {
this.name = name
}
public name: string = 'test'
private pri () {
}
protected pro() {}
readonly age: number = 20
static food: string = 'bones'
run() {
}
}
console.log(Dog.prototype)
const dog = new Dog('wangwang')
// dog.pri() // ❌
// dog.pro() // ❌
// dog.age = 21
// dog.food
console.log('food', Dog.food)
console.log(dog)
class Husky extends Dog {
constructor(name: string, color: string) {
super(name) // 父类的实例
this.color = color
// this.pri() // ❌
// this.pro() // ✅
}
color: string
}
console.log('Husky', Husky.food)
JS 的继承方式是原型式继承,原型上的属性和方法是所有实例共享的,不需要共享的就放在构造函数中(也就是实例自己的属性和方法)。当调用实例的属性或方法时,先看实例自身有没有,如果没有就会沿着原型链查找。
- public:对所有都是可见的,默认访问控制符
- protected: 修饰成员属性和方法,当前类和子类内部可以访问; 修饰构造方法, 该类不能被实例化,但可以被子类继承;只能在类本身或者子类中访问,不能在实例中访问
- protected constructor: 类只能被继承,不能被实例化,相当于基类
- private: 只能在类本身调用,不能被实例或者子类调用
- private constructor: 类不能被继承,也不能被实例化
- readonly: 修饰用来修饰只读属性, 必须设置初始值;必须要初始化,不能被修改
- static: 修饰的成员属性和方法, 只能被当前类和子类访问, 不能被类的实例访问;只能通过类名来调用,不能用实例来访问,可以被子类继承
抽象类与多态
抽象类只能被继承,无法创建实例
好处:
- 抽离事务的共性,有利于代码抽离复用
- 实现多态:父类中定义一个抽象方法,多个子类中对这个方法有不同的实现,程序运行时根据不同的实例对象执行不同的操作,运行时绑定
this 类型实现链式调用
class WorkFlow {
step1() {
console.log('Step1')
return this
}
step2() {
console.log('Step2')
return this
}
}
const workFlow = new WorkFlow()
// workFlow.step1().step2()
class MyFlow extends WorkFlow {
next () {
console.log('next')
return this
}
}
const myFlow = new MyFlow()
myFlow.next().step1().step2().next()
类与接口
工程篇
实战篇
待更新🚀...