Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript从0到1,进行中... #138

Open
smileyby opened this issue Aug 13, 2021 · 2 comments
Open

TypeScript从0到1,进行中... #138

smileyby opened this issue Aug 13, 2021 · 2 comments
Labels

Comments

@smileyby
Copy link
Owner

smileyby commented Aug 13, 2021

编译器的组成

  1. Scanner 扫描器
  2. Parser 解析器
  3. Binder 绑定器
  4. Emitter 发射器
  5. Checker 检查器

编译器的处理

  1. 扫描器通过扫描源代码生成 token流
  2. 解析器将token流解析为抽象语法树
  3. 绑定器将 语法树中的声明节点与相同实体的其他声明相连形成符号,符号是语义系统的主要构造块
  4. 检查器 通过符号和语法树来验证源代码语义
  5. 最后我们通过发射器生成javascript代码
@smileyby smileyby added the Todo 🐦 Todo label Aug 13, 2021
@smileyby
Copy link
Owner Author

typescript深入浅出 笔记

为什么要学习 typescript

  • 为大型系统而生:typescript 是静态类型化的javascript超集,在大型工程中有无可比拟的又是,是开发大型系统的必备良药,VSCode、VUE3.0、Angular都是由TS开发
  • 从2018年开始typescript就成为了github前10的语言,已经有大量大厂团队采用typescript开发
  • 随着VUE3.0 发布后,三大框架基本上离不开ts了

TS开发者的四个层级

  1. 业务使用者:可以再业务代码中熟练利用Typescript编码
  2. 类型编程者:可以对类型进行编程,可以开发一些使用的工具类型,对于难以定义的类型也驾轻就熟
  3. TS定制者:可以开发 typescript transformer plugin 来定制化开发 typescript
  4. TS设计者:参与typescript这门语言的设计

typescript 优势

  • 规避大量低级错误,避免时间浪费,省时;
  • 减少多人协作项目的成本,大型项目友好,省力;
  • 良好代码提示,不用反复文件跳转翻阅文档,省心;

章节1

{
  "compilerOptions": {
    "target": "es5",                            // 指定 ECMAScript 目标版本: 'ES5'
    "module": "commonjs",                       // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "moduleResolution": "node",                 // 选择模块解析策略
    "experimentalDecorators": true,             // 启用实验性的ES装饰器
    "allowSyntheticDefaultImports": true,       // 允许从没有设置默认导出的模块中默认导入。
    "sourceMap": true,                          // 把 ts 文件编译成 js 文件的时候,同时生成对应的 map 文件
    "strict": true,                             // 启用所有严格类型检查选项
    "noImplicitAny": true,                      // 在表达式和声明上有隐含的 any类型时报错
    "alwaysStrict": true,                       // 以严格模式检查模块,并在每个文件里加入 'use strict'
    "declaration": true,                        // 生成相应的.d.ts文件
    "removeComments": true,                     // 删除编译后的所有的注释
    "noImplicitReturns": true,                  // 不是函数的所有返回路径都有返回值时报错
    "importHelpers": true,                      // 从 tslib 导入辅助工具函数
    "lib": ["es6", "dom"],                      // 指定要包含在编译中的库文件
    "typeRoots": ["node_modules/@types"],
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": [                                  // 需要编译的ts文件一个*表示文件匹配**表示忽略文件的深度问题
    "./src/**/*.ts"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "**/*.test.ts",
  ]
}

章节2-Typescript 的原始类型

原始类型包括:boolean null undefined string number void symbol bigint

  • symbol 使用时 tsconfig.json 中需要添加 lib: ["es6"] 辅助编译库
  • bigint 使用时 tsconfig.json 中需要添加 lib: ["ESNext"] 辅助编译库

章节3-Typescript 中其他常见类型

  1. 顶级类型:any、unknown
  • any: 用于定义一些未知类型,如来源于第三方库的变量
  • unknown:Typescript 3.0 引入的新类型,相比于any更加严格
  let obj: unknown;
  obj.a.b;     // error
  obj();       // error
  new obj();   // error
  value[0][1]; // error
  1. 底部类型:never
  • never类型标识的是用不存在的值的类型,never类型是任何类型的子类型,也可以赋值给任何类型(没有类型是never的子类型或可以复制给never类型,除了never本身之外)
  • 两个常见never场景
  function throwError(message: string): never {
    throw new Error(message);
  }

  const emptyArr: never[] = [];
  1. 数组、元组等
  • 数组

    • 泛型定义:const list: Array<number> = [1,2,3]
    • 普通定义:const list: number[] = [1,2,3]
  • 元组:表示一个已知元素数量和类型的数组

      let a: [string, number];
      x = ['1', 2, null]; // error
      x = ['1']; // error
      x = ['1', 1] // ok

章节4-深入理解枚举类型

  1. 数字枚举
  enum Direction {
    up,  // 可指定初始值 up = 10;
    down,
    left,
    right
  }
  console.log(Direction.up); // 0
  console.log(Direction.down); // 1
  console.log(Direction.left); // 2
  console.log(Direction.right); // 3
  1. 字符串枚举
  enum Direction {
    Up = 'Up',
    Down = 'Down',
    Left = 'Left',
    Right = 'Right'
  }

  console.log(Direction['Right'], Direction.Up); // Right Up
  1. 异构枚举
  enum BooleanLikeHeterogeneousEnum {
    No = 0,
    Yes = "YES",
  }
  1. 反向映射
  emum Direction {
    Up, 
    Down,
    Left,
    Right
  }
  
  console.log(Direction.Up === 0); // true
  console.log(Direction.Down === 1); // true
  console.log(Direction.Left === 2); // true
  console.log(Direction.Right === 3); // true
  console.log(Direction[0] === up); // true
  1. 常量枚举
  const enum Direction {
    Up = 'Up',
    Down = 'Down',
    Left = 'Left',
    Right = 'Right'
  }

  const a = Direction.Up;
  // 如果要 typescript 保留对象 Direction,那么可以添加编译选项 --preserveConstEnums
  var a = 'Up';
  1. 联合枚举与枚举成员的类型

我们假设枚举的所有成员都是字面量类型的值,那么枚举的没个成员和枚举本身都可以作为类型来使用

章节5-接口

  1. 接口的使用
  • 可选属性、只读属性

章节6-类

1. 抽象类

```typescript
  abstract class Animal {
    abstract makeSound(): void;
    move(): void {
      console.log('sth')
    }
  }

  const animal = new Animal(); // 实例化会报错,不能直接实例化抽象类,通常需要我们创建子类继承基类然后可以实例化子类
```

2. 访问限定符

```typescript
  // public 公共属性或方法,可以被外部访问
  class Car {
    public a(){
      console.log('a')
    }

    private b(){
      console.log('b')
    }
  }
  const car = new Car()
  car.run();
  car.stop(); // error 属性 stop 是私有属性,只能在Car中访问
```

```typescript
  class Car {
    protected a(){
      console.log('a');
    }
  }

  class GTR extends Car {
    init(){
      this.a();
    }
  }
  const car = new Car();
  const gtr = new GTR();

  car.a() // [ts] 属性“a”受保护,只能在类“Car”及其子类中访问。
  gtr.init() // 启动...
  gtr.a() // [ts] 属性“a”受保护,只能在类“Car”及其子类中访问。
```

章节7-函数

  // 可选参数
  const add = (a:number, b?: number) => a+b

  // 剩余参数
  const add1 = (a:number, ...rest: number[]) => rest.reduce((a,b) => a+b, a)

  // 函数重载

  interface Direction {
    top: number,
    bottom?: number,
    left?: number,
    right?: number
  }
  function assigned(all: number): Direction
  function assigned(topAndBottom: number, leftAndRight: number): Direction
  function assigned(top: number, right: number, bottom: number, left: number): Direction

  function assigned (a: number, b?: number, c?: number, d?: number) {
    if (b === undefined && c === undefined && d === undefined) {
      b = c = d = a
    } else if (c === undefined && d === undefined) {
      c = a
      d = b
    }
    return {
      top: a,
      right: b,
      bottom: c,
      left: d
    }
  }

  assigned(1)
  assigned(1,2)
  assigned(1,2,3)
  assigned(1,2,3,4)

章节8-泛型的妙用

  // 单个类型
  function returnItem<T>(para: T): T {
    return para
  }

  // 多个类型
  function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]]
  }
  swap([7, 'seven'])

  // 类型变量
  function getArrayLength<T>(arg: Array<T>) {
    console.log((arg as Array<any>).length) // ok
    return arg
  }

  // 泛型接口
  interface ReturnItemFn<T> {
    (para: T): T
  }
  const returnItem: ReturnItemFn<number> = para => para
  // 泛型类
  class Stack {
    private arr: number[]: []

    public push(item: number) {
      this.arr.push(item)
    }

    public pop(){
      this.arr.pop()
    }
  }

  class Stack<T> {
    private arr: T[]: []

    public push(item: T) {
      this.arr.push(item)
    }

    public pop() {
      this.arr.pop()
    }
  }

  // 泛型约束
  type Params = number | string

  class Stack<T extends Params> {
    private arr: T[] = []

    public push(item: T) {
      this.arr.push(item)
    }

    public pop() {
      this.arr.pop()
    }
  }
  // 函数接受两个参数,一个参数为对象,另一个参数为对象上的属性,返回属性值
  function getValue(obj: object, key: string) {
    return obj[key] // error
  }

  // 上面报错,需要借助 keyof T 将对象的属性类型取出生成一个联合类型
  function getValue<T extends object, U extends keyof T>(obj: T, key: U) {
    return obj[key]
  }

  // 多重类型约束
  interface ChildInterface extends FirstInterface, SecondInterface {}
  class Demo<T extends ChildInterface>

  // 交叉类型多重约束
  class Demo<T extedns FirstInterface & SecondInterface>
  // 参数 type 的类型 {new(): T} 表示泛型T是可被构造的,在被实例化后的类型是泛型T
  function factory<T>(type: {new(): T}): T {
    return new type()
  }

章节9-类型断言与类型守卫

  // 类型断言
  const person = {}
  person.name = 'a' // Error: name 属性不存在与{}
  person.age = 20   // Error: age 属性不存在于{}

  interface Person {
    name: string;
    age: number;
  }
  const person = {} as Person
  person.name = 'a'
  person.age = 20

  // 双重断言
  const user = 'b' as any as Person;
  // 类型守卫
  instanceof 类型保护是通过构造函数来细化类型的一种方式

  class Person {
    name = 'a';
    age = 20;
  }

  class Animal {
    name = 'b';
    color = 'red'
  }

  function getSometing(arg: Person | Animal) {
    // 类型细化
    if (arg instanceof Person) {
      console.log(arg.color); // error 因为 arg 被细化为person 而person上不存在color属性
      console.log(arg.age);
    }

    if (arg instanceof Animal) {
      console.log(arg.age); // error
    }
  }
  // in: x in y 标识x属性存在于y中
  class Person {
    name = 'a';
    age = 20;
  }

  class Animal {
    name = 'b';
    color = 'red'
  }

  function getSometing(arg: Person | Animal) {
    if ('age' in arg) {
      console.log(arg.color); // error
    } 
    if ('color' in arg) {
      console.log(arg.age); // error
    }
  }
  // 字面量类型守卫
  type Foo = {
    name: 'foo';
    foo: number
  }

  type Bar = {
    kind: 'bar';
    bar: number;
  }

  function doStuff(arg: Foo | Bar) {
    if (arg.kind === 'foo') {
      console.log(arg.foo);
      console.log(arg.bar); // error
    } else {
      console.log(arg.foo); // error
      console.log(arg.bar);
    }
  }

章节10-类型兼容性

类型兼容性用于确定一个类型是否能赋值给其他类型

结构类型

class Person {
    constructor(public weight: number, public name: string, public born: string) {
        
    }
}

interface Dog {
    name: string;
    weight: number;
}
let x: Dog
x = new Person(120, 'a', '2021-01-01'); // person 包含 dog类所需的属性,所以不会报错

函数的类型兼容性

// 函数参数兼容性
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;

y = x; // ok
x = y; // Type '(b: number, s: string) => number' is not assignable to type '(a: number) => number'.
// 当我们把 strictNullChecks 设置为 false 时代码是兼容的。
let foo = (x: number, y: number) => {}
let bar = (x?: number, y?:number) => {}
let bas = (...args: number[]) => {}

foo = bar = bas;
bas = bar = foo;
  let foo = (x: number, y: number) => {}
  let bar = (x?: number) => {}

  foo = bar // ok
  bar = foo // Type '(x: number, y: number) => void' is not assignable to type '(x?: number) => void'.

枚举的类型兼容性

  enum Status {
    Ready,
    Waiting
  }
  let status = Status.Ready;
  let num = 0;

  status = num;
  num = status;

类的类型兼容性

  // 仅仅只有实例成员和方法会相比较,构造函数和静态成员不会被检查

  class Animal {
    feet: number;
    constructor(name: string, numFeet: number) {}
  }

  class Size {
    feet: number;
    constructor(meters: number) {}
  }

  let a: Animal;
  let s: Size;
  a = s;
  s = a;
// 私有的和受保护的成员必须来自相同的类
class Animal {
  protected feet: number;
}

class Cat extends Animal {}

let animal: Animal;
let cat: Cat;

animal = cat;
cat = animal;

class Size {
  protected feet: number;
}
let size: Size;

animal = size; // error
size = animal; // error

泛型的类型兼容性

  // 泛型本身就是不确定的类型,它的表现根据是否被成员使用而不同
  interface Person<T> {}

  let x: Person<string>
  let y: Person<number>

  x = y // ok
  y = x // ok
  interface Person<T>{
    name: T
  }
  let x: Person<string>
  let y: Person<number>

  x = y;
  y = x; // Type 'Person<number>' is not assignable to type 'Person<string>'.Type 'number' is not assignable to type 'string'
  interface Person {
    name: string;
    age: number;
    weight: number;
  }

  interface Animal {
    name: string;
    age: number;
    weight: number;
  }

  function getPersonName(p: Person) {
    // 如何在传入animal的时候报错
  }

章节11-高级类型:交叉类型、联合类型、类型别名

交叉类型

交叉类型是将多个类型合并为一个类型。折让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性

  interface IAnyObject {
    [prop: string]: any
  }

  function mixin<T extends IAnyObject, U extends IAnyObject>(first: T, second: U): T & U {
    const result = <T & U>{};
    for(let id in first) {
      (<T>result)[id] = first[id]
    }

    for(let id in second) {
      if (!result.hasOwnProperty(id)) {
        (<U>result)[id] = second[id];
      }
    }

    return result;
  }

  const x = mixin({a: 'hello'}, {b: 42});
  const a = x.a;
  const b = x.b;

联合类型

  // 联合类型:我们用竖线分隔没个类型
  function formatCommandline(command: string[] | string) {
    let line = '';
    if (typeof command === 'string') {
      line = command.trim()
    } else {
      line = command.join('').trim();
    }
  }  

类型别名

类型别名会给一个类型起个新名字,类型别名有时和接口很像,但是可以作用于原始值、联合类型、元组以及其他任何你需要手写的类型

  type some = boolean | string;
  const b: some = true // ok
  const c: some = 'hello' // ok
  const d: some = 123 // 不能将类型“123”分配给类型“some”

  // 类型别名:泛型
  type Container<T> = { value: T };

  // 类型别名:可以再属性里引用自己
  type Tree<T> = {
    value: T;
    left: Tree<T>;
    right: Tree<T>;
  }

类型别名和interface很像,区别是:

  1. interface 只能用于定义对象类型
  2. type的声明方式除了对象之外还可以定义交叉联合、原始类型、类型声明的方式适用范围更广
  3. intrface 方式可以实现接口的 extends 和 implements
  4. interface 可以实现接口合并声明
type Alias = { num: number }

interface Interface {
  num: number
}

declare function aliased(arg: Alias): Alias
declare function interfaced(arg: Interface): interface;

章节12-可辨识联合类型

前置概念:

字面量类型

  const a: 2333 = 2333 // ok
  const ab : 0b10 = 2 // ok
  const ao : 0o114 = 0b1001100 // ok
  const ax : 0x514 = 0x514 // ok
  const b : 0x1919n = 6425n // BigInt literals are not available when targeting lower than ES2020
  const c : 'xiaomuzhu' = 'xiaomuzhu' // ok
  const d : false = false // ok

  const g: 'github' = 'pronhub' // Type '"pronhub"' is not assignable to type '"github"'

单个类型的字面量类型用途:

  type Direction = 'North' | 'East' | 'South' | 'West';

  function move(distance: number, direction: Direction) {
      // ...
  }

类型字面量

  type Foo = {
    baz: [
      number,
      'xiaomuzhu'
    ];
    toString(): string;
    readonly [Symbol.iterator]: 'github';
    0x1: 'foo';
    'bar': 12n;
  }

可辨识联合类型

  interface Info {
    username: string
  }

  interface UserAction {
    id?: number
    action: 'create' | 'delete'
    info: Info
  }

  // 在创建的时候id是不需要传的,但是下面的结构确实合法的
  const action: UserAction = {
    action: 'create',
    id: 111,
    info: {
      username: 'xiaomuzhu'
    }
  }
  // 这时需要 使用类型字面量 |
  type UserAction = | {
    id: number
    action: 'delete'
    info: Info
  } | {
    action: 'create'
    info: Info
  }

章节13-装饰器

装饰器的主要作用:给一个已有的方法或类型扩展一些新的行为,而不是取直接修改它本身

装饰器目前扔处于 草案阶段,需要我们在 javascript中安装 babel插件:babel-plugin-transform-decorators-legacy 来支持 decorator,而在 Typescript中我们需要在 tsconfig.json 里面开启支持选项 experomentalDecorators

  1. 目前装饰器本质上是一个函数 @expression 形式其实是一个语法糖, expression求值后必须是一个函数,它会在运行时被调用,被装饰的声明信息作为参数传入
  2. javascript 中的class 其实也是一个语法糖
class Person {
  say(){
    console.log('hello')
  }
}

function Person(){}
Object.defineProperty(Person.prototype, 'say', {
  value: function(){ console.log('hello') },
  enumerable: false,
  configurable: true,
  writable: true
})

类装饰器

  function addAge(constructor: Function) {
    constructor.prototype.age = 18;
  }

  @addAge
  class Person {
    name: string;
    age!: number;
    constructor(){
      this.name = 'xiaomuzhu'
    }
  }

  let person = new Person();
  console.log(person.age); // 18

属性、方法装饰器

  function method(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    consle.log(target);
    console.log('prop' + propertyKey)
    console.log('desc' + JSON.stringify(descriptor))
    descriptor.writable = false;
  }

  class Person {
    name: string;
    constructor(){
      this.name = 'xiaomuzhu'
    }
  }

  @method
  say(){
    return 'instance method'
  }

  @method
  static run(){
    return 'static method'
  }
  const xmz = new Person()

  xmz.say = function(){
    return 'edit'
  }

  console.log(xmz.say())

章节-14 高级装饰器

参数装饰器

报错:Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning.

vscode 死活不识别这玩意,我服了

function logParameter(target: object, propertyKey: string, index: number) {
  console.log(target, propertyKey, index)
}

class Person {
  greet(@logParameter message: string, @logParameter name: string): string {
    return `${message} ${name}`
  }
}
const p = new Person();
p.greet('hello', 'xiaomuzhu')

装饰器工厂

@logClass
class Person { 

  @logProperty
  public name: string;

  constructor(name : string) { 
    this.name = name;
  }

  @logMethod
  public greet(@logParameter message : string) : string { 
    return `${this.name} say: ${message}`;
  }
}

// 打印构造函数
function logClass(target: typeof Person) {
    console.log(target)
}

// 打印属性名
function logProperty(target: any, propertyKey: string) {
    console.log(propertyKey);   
}

// 打印方法名
function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log(propertyKey);   
}

// 打印参数位置
function logParameter(target: Object, propertyKey: string, index: number) {
    console.log(index);
}

// name
// 0
// greet
// [Function: Person]
function log(...args: any[]) {
  switch(args.length) {
    case 1:
      return logClass.apply(this, args)
    case 2:
      return logProperty.apply(this, args)
    case 3: 
      if (typeof args[2] === 'number') {
        return logParameter.apply(this.args)
      }
      return logMethod.apply(this. args)
    default:
      thorw new Error('Decorators are not valid here!')
  }
}

@smileyby
Copy link
Owner Author

typescript

简介

  1. 什么是typescript?

    • 定义:TypeScript 是一种基于 JavaScript 的强类型编程语言;是添加了类型系统的 javascript,适用于任何规模的项目;TypeScript 是一门静态类型、弱类型的语言

    • 特性:

      • 类型系统

        • 按照 类型检查的时机 分为 动态类型:即运行时检测 和静态类型:编译阶段检测

          // js
          let foo = 1;
          foo.split(' ');
          // Uncaught TypeError: foo.split is not a function
          // 运行时会报错(foo.split 不是一个函数),造成线上 bug
          // ts
          let foo: number = 1;
          foo.split(' ');
          // Property 'split' does not exist on type 'number'.
          // 编译时会报错(数字没有 split 方法),无法通过编译
        • 按是否允许隐式类型转换 分为 强类型和弱类型

  2. hello typescript

// function sayHello(person: string): string (+1 overload)
// Duplicate function implementation.ts(2393)
function sayHello(person: string){
  return 'Hello, ' + person;
}

// let user: string
// Cannot redeclare block-scoped variable 'user'.ts(2451)
let user = 'Tom';
console.log(sayHello(user));

// 上面两处告警,是说声明的变量可能在全局被覆盖,ts不建议这么写,可以在代码 顶部 添加 export {} 解决
function sayHello(person) {
    return 'Hello, ' + person;
}
var user = 'Tom';
console.log(sayHello(user));

基础

原始数据类型

原始数据类型包括:布尔值、数值、字符串、null、undefined、Symbol、BigInt

let isDone: boolean = false;
let createdByNewBoolean: Boolean = new Boolean(1);
let createdByNewBoolean: boolean = Boolean(1);
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
// ES6 中的二进制表示法
let binaryLiteral: number = 0b1010;
// ES6 中的八进制表示法
let octalLiteral: number = 0o744;
let notANumber: number = NaN;
let infinityNumber: number = Infinity;
let myName: string = 'Tom';
let myAge: number = 25;

// 模板字符串
let sentence: string = `Hello, my name is ${myName}.
I'll be ${myAge + 1} years old next month.`;
// void 表示没有任何返回值的函数
function alertName(): void {
  alert('my name is tom');
}

// 声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null
let unnasble: void = undefined;
let u: undefined = undefined;
let n: null = null;

// 由于 undefined 和 null 是所有类型的子类型,所以undefined类型可以复制给number类型
let num: number = undefined;

// 而void 类型的变量不能赋值给 number 类型的变量
let v: viod;
let num: number = u; // Type 'void' is not assignable to type 'number'.

任意值

如果是一个普通类型,在赋值过程中改变类型是不被允许的

let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7; // error TS2322: Type 'number' is not assignable to type 'string'.
let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;
// 在任意值上访问任何属性都是被允许的
let anyThing: any = 'hello';
console.log(anyThing.myName);
console.log(anyThing.myName.firstName);
// 也允许调用任何方法
let anyThing: any = 'Tom';
anyThing.setName('Jerry');
anyThing.setName('Jerry').sayHello();
anyThing.myName.setFirstName('Cat');

声明一个变量未任意值,对它的任何操作,返回的内容的类型都是任意值

// 变量如果在声明的时候,未指定其类型,那么他会被识别为任意类型
let something; // 等价于  let something: any;
something = 'seven';
something = 7;

something.setName('Tom')

类型推论

typescript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论
如果定义的时候没有复制,不管之后有没有被复制,都会被推断成 any 类型而完全不被类型检查

let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

// error TS2322: Type 'number' is not assignable to type 'string'.

// 以上代码等价于
// let myFavoriteNumber: string = 'seven';
// myFavoriteNumber = 7;

联合类型

联合类型:标识取值可以为多种类型中的一种

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
let myFavoriteNumber: string | number;
myFavoriteNumber = true; // error TS2322: Type 'boolean' is not assignable to type 'string | number'.

当typescript不确定一个联合类型的变量到底是那个类型的时候,我们只能访问此联合类型的所有类型里 共有的属性或方法

// 由于 length 不是 string 和 number 的共有属性,所以会报错
function getLength(something: string | number): number {
  return something.length; // error TS2339: Property 'length' does not exist on type 'string | number'.
}

联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length); // 5
myFavoriteNumber = 7;
console.log(myFavoriteNumber.length); // error TS2339: Property 'length' does not exist on type 'number'.

对象的类型-接口

interface Person {
  name: string;
  age: number;
}

let tom: Person = {
  name: 'Tom',
  age: 25
}
interface Person {
    name: string;
    age: number;
}

let tom: Person = {
    name: 'Tom'
};
// error TS2322: Type '{ name: string; }' is not assignable to type 'Person'.
//   Property 'age' is missing in type '{ name: string; }'.
interface Person {
    name: string;
    age: number;
}

let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};
// error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
//   Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.

在赋值的时候,变量的形态必须和接口的形态保持一致

interface Person {
  name: string;
  age?: number;
}

let tom: Person = {
    name: 'Tom';
    age: 25;
};
interface Person {
    name: string;
    age?: number;
}

let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};
// error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
//   Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.

需要注意的是:一旦定义了任意属性,name确定属性和可选属性的类型必须都是它的类型的子集

interface Person {
  name: string;
  age?: number;
  [propName: string]: string;
}

let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};
// error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
//   Index signatures are incompatible.
//     Type 'string | number' is not assignable to type 'string'.
//       Type 'number' is not assignable to type 'string'.
interface Person {
  name: string;
  age?: number;
  [propName: string]: string | number;
}

let tom: Person = {
  name: 'Tom',
  age: 25,
  gender: 'male'
}
interface Person {
    readonly id: number;
    name: string;
    age?: number;
    [propName: string]: any;
}

let tom: Person = {
    id: 89757,
    name: 'Tom',
    gender: 'male'
};

tom.id = 9527;
//  error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.

注意,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候(即 tom.id = xxxx 会触发只读约束)

interface Person {
  readonly id: number;
  name: string;
  age?: number;
  [propName: string]: any;
}

let tom: Person = {
  id: 89757,
  name: 'Tom',
  gender: 'male'
};

tom.id = 9527;

数组的类型

let fibonacci: number[] = [1,1,2,3,5];
let fibonacci: number[] = [1, '1', 2];
// // Type 'string' is not assignable to type 'number'.
let fibonacci: number[] = [1,1,2,3,5];
fibonacci.push('8');
// Argument of type '"8"' is not assignable to parameter of type 'number'.
let fibonacci: Array<number> = [1,1,2,3,5];
interface NumberArray {
  [index: number]: number
}
let fibonacci: NumberArray = [1,1,2,3,5]
function sum(){
  let args: number[] = arguments;
}
// Type 'IArguments' is missing the following properties from type 'number[]': pop, push, concat, join, and 24 more.
// 类数组,实际不是数组不能用普通数组的方式来描述,而应该用接口
// 常用的类数组都有自己的接口定义,如:IArguments NodeList HTMLCollection等
function sum(){
  let args: {
    [index: number]: number;
    length: number;
    callee: Function;
  } = arguments;
}
// any 允许数组中出现任意类型
let list: any[] = ['a', 2, {c: 'x'}]

函数的类型

在JavaScript中,有两种常见的定义函数的方式:函数声明、函数表达式
一个函数有输入和输出,要在 typescript 中对其进行约束,需要把输入和输出都考虑到,其中函数声明的类型定义比较简单

function sum(x: number, y: number): number {
  return x + y;
}
function sum(x: number, y: number): number {
  return x + y;
}

sum(1, 2, 3);
sum(1);
// error TS2346: Supplied parameters do not match any signature of call target.

// 输入多余或者少于要求的参数,是不被允许的
// typescript 中 => 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型
let mySum: (x: number, y: number) => number = function(x: number, y: number): number {
  return x + y;
}
// 采用函数表达式接口定义函数的方式时,对等号左侧进行类型显示,可以保证以后对函数名复制时,保证参数个数、参数类型、返回值类型不变
interface SearchFunc {
  (source: string, subString: string): boolean
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string): boolean{
  return source.search(subString) !== -1;
}
// 可选参数
// 需要注意的是,可选参数一定要在必选参数后面
function buildName(firstName: string, lastName?: string) {
  if (lastName) {
    return firstName + ' ' + lastName;
  } else {
    return firstName;
  }
}
let tomcat = buildName('Tom', 'Cat');
let tom = build('Tom');
function buildName(firstName?: string, lastName: string) {
    if (firstName) {
        return firstName + ' ' + lastName;
    } else {
        return lastName;
    }
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName(undefined, 'Tom');

// error TS1016: A required parameter cannot follow an optional parameter.
// 参数默认值
// 在ES6中,我们允许给函数的参数添加默认值, typescript会将添加了默认值的参数识别为可选参数
function buildName(firstName: string, lastName: string = 'Cat') {
  return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
// 此时,就不受 可选参数必须接到必需参数后面 的限制了
function buildName(firstName: string = 'Tom', lastName: string) {
  return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let cat = buildName(undefined, 'Cat');
// 需要注意的是,剩余参数只能是最后一个参数
function push(array, ...items) {
  items.forEach(function(item){
    array.push(item)
  })
}
let a: any[] = [];
push(a, 1, 2, 3);
function push(array: any[], ...items: any[]) {
  items.forEach(function(item){
    array.push(item);
  })
}
let a = [];
push(a, 1, 2, 3);
// 重载
// 重载允许一个函数接受不同数量或类型的参数,做出不同的处理

function reverse(x: number | string): number | string | void {
  if (typeof x === 'string') {
    return Number(x.toString().split('').reverse().join(''))
  } else if (typeof x === 'string') {
    return x.split('').reverse('').join('')
  }
}

// 这样有一个缺点,就是不能够精准的表达,输入数字的时候输出也应该是数字,输入字符串是,驶出也应该是字符串
// 使用多重 重载定义
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string | void {
  if (typeof x === 'string') {
    return Number(x.toString().split('').reverse().join(''))
  } else if (typeof x === 'string') {
    return x.split('').reverse('').join('')
  }
}

typescript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面

类型断言

// 语法
 as 类型

<类型>
interface Cat {
  name: string;
  run(): void;
}

interface Fish {
  name: string;
  swim(): void;
}

function isFish(animal: Cat | Fish) {
  if (typeof animal.swim === 'function') {
    return true;
  }
  return false;
}

// error TS2339: Property 'swim' does not exist on type 'Cat | Fish'.  Property 'swim' does not exist on type 'Cat'.
// 类型断言只能够欺骗,typescript编译器,无法避免运行时的错误
interface Cat {
  name: string;
  run(): void;
}

interface Fish {
  name: string;
  swim(): void;
}

function isFish(animal: Cat | Fish) {
  if (typeof (animal as Fish).swim === 'function') {
    return true;
  }
  return false;
}
class ApiError extends Error {
  code: number = 0;
}
class isApiError(error: Error){
  if (error instanceof ApiError) {
    return true;
  }
  return false;
}

将任何一个类型断言为 any

(window as any).foo = 1;
// 遇到 any 类型的变量时,我们可以选择通过类型断言 及时把 any 断言为 精确的类型
functon getCacheData(key: string): any{
  return (window as any).cache[key];
}

interface Cat {
  name: string;
  run(): void;
}

const tom = getCacheData('tom') as Cat;
tom.run();

类型断言的限制

  • 联合类型可以被断言为其中的一个类型
  • 父类可以被断言为子类
  • 任何类型都可以被断言为any
  • any 可以被断言为任何类型
interface Animal {
  name: string;
}

interface Cat {
  name: string;
  run(): void;
}

let tom: Cat = {
  name: 'Tom',
  run: () => { console.log('run') }
}
let animal: Animal = tom;

在上面的例子中,Cat包含了 Animal 中的所有属性,除此之外,它还有一个额外的方法 run。typescript 并不关心 Cat 和 Animal 之间定义时是什么关系,而只会看他们最终的结构有什么关系 所以它 与 Cat extends Animal 是等价的

interface Animal {
  name: string;
}
interface Cat extends Animal {
  run(): void;
}
interface Animal {
  name: string;
}
interface Cat {
  name: string;
  run(): void;
}

function testAnimal(animal: Animal) {
  return (animal as Cat);
}

function testCat(cat: Cat){
  return (cat as Animal);
}

总上所述:

  • 联合类型可以被断言为其中一个类型
  • 父类可以被断言为子类
  • 任何类型都可以被断言为 any
  • any可以被断言为任何类型
  • 要使得A能够被断言为B,只需要A兼容B或B兼容A即可
interface Cat {
  run(): void;
}
interface Fish {
  swim(): void;
}

function testCat(cat: Cat) {
  return (cat as any as Fish);
}

// 若你使用了这种双重断言,很可能导致运行时错误
// 类型断言只会影响 typescript 编译时的类型,类型断言语句在编译结果中会被删除
function toBoolean(something: any): boolean {
  return something as boolean
}

toBoolean(1);
function toBoolean(something) {
  return something;
}
toBoolean(1);

// 在上面例子中将 something 断言为 boolean 虽然可以通过编译,但是并没有什么用,代码最后编译如上
// 所以类型断言不是类型转换,它不会影响到变量的类型
function getCacheData<T>(key: string): T {
  return (window as any).cache[key];
}

interface Cat {
  name: string;
  run(): void;
}

const tom = getCacheData<Cat>('tom')
tom.run();

声明文件

声明语句,指定引用第三方库的引用变量类型

declare var jQuery: (selector: string) => any;

jQuery('#foo')

通常我们会把声明语句放在一个单独的文件(file.d.ts)中,且声明文件必须以 .d.ts 为后缀

第三方声明文件,推荐使用 @types 统一管理第三方库的声明文件

npm install @types/jquery --save-dev

这块有点复杂

内置对象

// 内置对象
let b: Boolean = new Boolean(1);
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/;

// dom 和 bom 的内置对象
// Document HTMLElement Event NodeList

let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
  // do something
})
Math.pow(10, '2');

// error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

document.addEventListener('click', function(e) {
    console.log(e.targetCurrent);
});
// error TS2339: Property 'targetCurrent' does not exist on type 'MouseEvent'.```@types/node --save-dev

进阶

类型别名

// 类型别名
// 类型别名常用与联合类型
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
  if (typeof n === 'string') {
    return n;
  } else {
    return n();
  }
}

字符串字面量类型

// 字符串字面量类型
type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
  // do something
}
handleEvent(document.getElementById('hello'), 'scroll');
handleEvent(document.getElementById('world'), 'dbclick'); // error TS2345: Argument of type '"dblclick"' is not assignable to parameter of type 'EventNames'.

需要注意的是:类型别名和字符串字面量类型都是使用 type 进行定义的

元组

// 定义一堆值分别为 string 和 number 的元组
let tom: [string, number] = ['Tom', 25];
let tom: [string, number];
tom = ['Tom', 25];

tom.push('male');
tom.push(true);

// error TS2345: Argument of type 'boolean' is not assignable to parameter of type 'string | number'.

// 当添加一个越界元素时,它的类型会被限制为元组中没个类型的联合类型

枚举

// 枚举(Enum)类型用于取值被先定在一定范围内的场景,比如一周只能有7天,颜色限定为红绿蓝等。

enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};

console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true

console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true
// 手动复制,未被复制的枚举项会接着上一个枚举项递增
enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};

console.log(Days["Sun"] === 7); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true
enum Days {Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat};

console.log(Days["Sun"] === 3); // true
console.log(Days["Wed"] === 3); // true
console.log(Days[3] === "Sun"); // false
console.log(Days[3] === "Wed"); // true

类与接口

泛型

声明合并

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
  if (typeof x === 'number') {
      return Number(x.toString().split('').reverse().join(''));
  } else if (typeof x === 'string') {
      return x.split('').reverse().join('');
  }
}
interface Alarm {
  price: number;
}
interface Alarm {
  weight: number;
}

// 相当于
interface Alarm {
  price: number;
  weight: number;
}
interface Alarm {
  price: number;
}
interface Alarm {
  price: string;
  weight: number;
}

//  error TS2403: Subsequent variable declarations must have the same type.  Variable 'price' must be of type 'number', but here has type 'string'.
interface Alarm {
  price: number;
  alert(s: string): string
}

interface Alarm {
  weight: number;
  alert(s: string, n: number): string;
}

// 相当于
interface Alarm {
  price: number;
  weight: number;
  alert(s: string): string;
  alert(s: string, n: number): string;
}

类的合并与接口的合并规则一致

代码检查

目前以及将来的TypeScript的代码检查方案就是 typescript-eslint

@smileyby smileyby changed the title TODO:TypeScript TypeScript从0到1,进行中... Jan 21, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant