重学TypeScript

基础

强类型语言:不允许改变数据类型,除非强制转换数据类型
弱类型语言:变量允许被赋予任意类型
静态类型语言:在编译阶段确定所有变量的类型
动态类型语言:在执行阶段确定所有变量的类型

# 创建 ts 配置文件 tsconfig.json
$ tsc --init

基本数据类型

Typescript    JavaScript
Boolean        Boolean
Number         Number
String         String
Array          Array
Function       Function
Object         Object
Symbol         Symbol
undefined      undefined
null           null
void
any
never
元组
枚举
高级类型

类型注解

// 作用:类似强类型语言中的类型声明
// 语法
[变量|函数]: type
  • null 和 undefined 是所有类型的子集
  • | 是联合类型
枚举类型
  • 枚举是一组有名字的常量集合
  • 枚举成员的值是只读的
  • 枚举成员分为常量(const)成员和计算(computed)成员
    • 计算成员的值不会在非编译阶段进行计算,而会被保留到成员的执行阶段
  • 改造那些代码为枚举类型
    • 可读性差:魔法数多
    • 可维护性差:硬编码过多,牵一发动全身
  • 枚举的实现原理 —— 反向映射
    • 枚举成员的名称作为 key
    • 枚举成员的值作为 value
    • 而后枚举成员的 value 作为 key
    • 枚举成员的 key 作为 value
enum _enum{
  成员
}

// JS 描述枚举的方式
let _enum
(function (_enum) {
  _enum[_enum["成员"] = 1] = "成员";
}
)(_enum || (_enum = {}))
  • 什么时候用?
    将程序中不容易记忆的硬编码或者是在未来中可能改变的常量抽取出来定义成枚举类型这样可以提高我们程序的可读性和可维护性。
函数的定义
function add1(x: number, y: number ){
  return x + y;
}
let add2: (x: number, y: number) => number
type add3 = (x: number, y: number) => number;
interface add4 {
  (x: number, y: number): number
}
// 重载
function add3(...rest: number[]): number;
function add3(...rest: string[]): string;
function add3(...reset: any[]): any {
  if (typeof reset[0] === 'number') {
    return 1;// TODO
  }
  if (typeof reset[0] === 'string') {
    return '1'; //TODO
  }
}

类的定义

// 与 ES 不同的是 为成员属性添加了类型注解
// 添加了 run 方法 默认返回值是 void
// 构造函数的返回值默认是 Dog 也就是这个类本身

class Dog {
  constructor(name: string){
    this.name = name;
  }
  name: string
  run(){}
}
// 不包含内部属性
console.log(Dog.prototype) // {run: f, constructor: f}
// 内部属性在实例上,不在原型上
// 实例的属性必须具有初始值,或在构造函数中被初始化	 
let dog = new Dog('wangwang');
console.log(dog);// Dog {name: 'wangwang'}

class Husky extends Dog {
  constructor(name: string, color: string) {
    super(name);
    this.color = color;
  }
  color: string
  // 类的成员修饰符,是对ES的扩展
  // ES 中默认都是 public 
  // private 不能被实例和子类调用
  // private constructor 表示既不能被实例化也不能被继承
  // protected 只能在类和子类中访问
  // protected constructor 表示只能被继承,不能被实例化,声明了一个基类
  // readonly 不能被更改,一定要被初始化
  // static 只能通过类名调用
}

// 抽象类 TS 对 ES 的扩展
// 只能被继承,而不能被实例化的类
abstract class Animal {
  // 抽象类中的方法可以有具体的实现
  eat() {
    console.log('eat');
  }
  // 抽象类中的方法也可以不具体实现,称为抽象方法
  // 抽象方法的好处是可以在子类中有其他的实现
  abstract sleep(): void 
}
// 抽象类可以实现多态
// 多态: 在父类中定义一个抽象方法,在多个子类中对这个方法有不同的实现
class Cat extends Animal {
  sleep(){
    console.log('cat cleep');
  }
}

let cat = new Cat();
let animals: Animal[] = [dog, cat];

// TS 的this 类型,实现链式调用

class WorkFlow {
  work1(){
    // TODO
    return this;
  }
  work2(){
    // TODO
    return this;
  }
}
new WorkFlow().work1().work2()

class Myflow extends WorkFlow {
  next(){
    return this;
  }
}
// 子父类联合调用
new Myflow().next().work1().next();

接口的定义
// 类类型接口
interface Human {
  name: string;
  eat(): void;
}

// 类实现接口时必须实现接口中声明的所有属性
// 接口只能约束类的公有(public)成员
// 接口不能约束 constructor
class Asian implements Human {
  constructor(name: string) {
    this.name = name;
  }
  name: string
  eat() {}
}

// 接口可以和类一样 相互继承
// 一个接口可以继承多个接口
interface Man extends Human {
  run(): void
}
interface Child { 
  cry(): void
}

interface Boy extends Man, Child {}
let boy: Boy {
  name: 'leeyw',
  run() {},
  eat() {},
  cry() {}
}

// 接口只有成员结构,但没有具体的实现
class Auto {
  state = 1
}
interface AutoInterface extends Auto {}

class C implements AutoInterface {
  state = 1
}

class Bus extends Auto implements AutoInterface {
  // 这里不需要设置 state 属性,因为 Bus 继承了 Auto ,作为 Auto 子类也继承了 state 属性
} 

重学TypeScript_第1张图片

泛型的概念

泛型: 不预先确定的数据类型,具体的类型在使用的时候才能确定。
好处

  1. 函数和类可以轻松地支持多种类型,增强程序的扩展性
  2. 不必写多条函数重载,冗长的联合类型声明,增强代码可读性
  3. 灵活控制类型之间的约束
// Q: 为什么不用 any
// A: any 类型丢失了一些信息,也就是类型之间的约束关系,忽略了输入参数类型和函数的返回值类型必须是一致的
// 声明
function fn<T, C>(k: T, C: V): T {
  return k;
}
// 调用
// 调用时指明
fn<number, number>(1, 2);
// 调用时利用 ts 的类型推断
fn(1, 2);

// 定义泛型函数类型
type Log = <T>(value: T) => T
let myLog: Log = log;

// 定义泛型接口
interface Log {
  <T>(value: T): T
}
// 有默认类型
interface Log <T> {
 (value: T): T
}
let myLog: Log<number> = log;
// 没有默认类型
interface Log <T = string> {
 (value: T): T
}
let myLog: Log = log;

// 泛型约束类的成员
// 泛型不能约束 static 成员
class Log<T> {
  run(value: T) {
    console.log(value);
    return value;
  }
}

let log1 = new Log<number>();
let log2 = new Log();

interface Length {
  length: number
}
function log<T extends Length>(value: T): T {
  console.log(value, value.length);
  return value
}

log([]); // √
log('11'); // √
log(1); // ×  没有 length 属性
TS 类型检查机制

Typescript 类型检查机制: TypeScript 编译器在做类型检查时,所秉承的一些原则,以及表现出的行为。辅助开发,提高开发效率;类型推断、类型兼容、类型保护

类型推断

不需要指定变量的类型(函数返回值类型),TypeScript 可以根据某些规则自动地为其推断出一个类型。

  • 基础类型推断
let a = 1;
let b = [1];
let c = (x = 1) => x + 1;
  • 最佳通用类型推断

当需要从多个类型中推断出一个类型时, TS 会尽可能的推断出一个兼容当前所有类型的通用类型

let b = [1, null]; // let b: (number | null)[]
// tsconfig.json
// "strictNullChecks": false
let b = [1, null]; // let b: number[]
  • 上下文类型推断
window.onkeydown = (event) => {
} // (parameter) event: KeyboardEvent

// 类型断言
interface Foo {
  bar: number
}
let foo = {} as Foo;
foo.bar = 1
 
类型兼容

当一个类型 Y 可以被赋值给另一个类型 X 时,我们就可以说 X 兼容 Y
X 兼容 Y: X(目标类型) = Y(源类型)
类型兼容性的例子广泛存在于,接口,函数,类中

// 接口兼容性
interface X {
  a: any;
  b: any;
}
interface Y {
  a: any;
  b: any;
  c: any;
}
let x: X = {a: 1, b: 2}
let y: Y = {a: 1, b: 2, c: 3}
x = y // √
y = x // ×

// 函数兼容性
type Handler = (a: number, b: number) => void
function hof(handler: Handler) {
  return handler;
}
// 1) 参数个数,目标函数(Handler)参数个数多于原函数(hof)参数个数

// 2) 参数类型
interface Point3D {
  x: number;
  y: number;
  z: number;
}
interface Point2D {
  x: number;
  y: number;
}
let p3d = (point: Point3D) => {}
let p2d = (point: Point2D) => {}
p3d = p2d; // :)
p2d = p3d; // :(

// 3) 返回值类型 目标函数的返回值类型必须与原函数返回值类型相同,或者为其子类型
类型保护

TypeScript 能够在特定的区块中保证变量属于某种确定类型
可以在此区块中放心的引用此类型的属性,或者调用此类型方法


enum Type { Strong, Week}

class Java {
  helloJava() {
    console.log('Hello Java');
  }
}
class JavaScript {
  helloJavaScript() {
    console.log('Hello JavaScript');
  }
}

function getLanguage(type: Type) {
  let lang = type === 'Type.Strong' ? new Java() : new JavaScript();
  
  // 使用类型断言 麻烦!
  // if ((lang as Java).helloJava) (lang as Java).helloJava();
  // else (lang as JavaScript).helloJavaScript();
  
  // 使用 instanceof 判断某个实例是不是属于某个类
  // if (lang instanceof Java) {
  //  // 在这个区块中,可以保证 lang 一定是 Java 实例
  // } else {
  //  // 在这个区块中,可以保证 lang 一定是 JavaScript 实例
  // }
  
  // 使用 in 关键词, 可以判断某个属性是不是属于某个对象
}

工程

编译工具 -> ts-loader / @babel/preset-typescript
代码检测工具 -> eslint + typescript-eslint / babel-eslint
单元测试 -> ts-jest / babel-jest

使用 Jest 进行单元测试

// math.ts
function add(a: number, b: number): number {
  return a + b;
}

function sub(a: number, b: number): number {
  return a - b;
}

module.exports = {
  add,
  sub
}

// test/math.test.ts
// 使用 ts-jest 可以在测试中进行类型检查
const math = require('src/math');

test('add: 1 + 1 = 2', () => {
  expect(math.add(1, 1)).toBe(2);
})

test('sub 1 - 2 = -1', () => {
  expect(math.sub(1, 2)).toBe(-1);
})

你可能感兴趣的:(重学TypeScript)