TypeScript|笔记

108 阅读9分钟

TypeScript

为什么要学习TS

TypeScript VS JavaScript

TS与JS的差异总结:TS是JS的扩展,是超集,TS也可以编译成JS运行,TS在编译的环节就可以发现一些问题,并且支持静态代码检测,支持后端语言中的一些相关特性,这是TS语言的核心能力

TypeScript带来了什么

  • 类型安全
  • 下一代JS特性
  • 完善的工具链

TS不仅仅是一种语言,更是生产力的工具

TypeScript推荐

TS基础

基础类型

  • boolean,number,string

  • 枚举enum 特有类型

    • 普通枚举

      enum Direction {
          Up,
          Down,
          Left,
          Right
      }
      ​
      console.log(Direction.Up); // 输出:0
      console.log(Direction.Down); // 输出:1
      console.log(Direction.Left); // 输出:2
      console.log(Direction.Right); // 输出:3
      
    • 字符串枚举

      enum Color {
          Red = 'FF0000',
          Green = '00FF00',
          Blue = '0000FF'
      }
      ​
      console.log(Color.Red); // 输出:"FF0000"
      console.log(Color.Green); // 输出:"00FF00"
      console.log(Color.Blue); // 输出:"0000FF"
      
    • 常量枚举

      • 如果你在枚举成员后面添加 = 符号和值,TypeScript 会将这些成员视为常量。常量枚举在编译阶段会被替换为它们的值,这可以减少代码体积。
      const enum Difficulty {
           Easy = 1,
           Medium,
           Hard
      }
      ​
      console.log(Difficulty.Easy); // 输出:1
      console.log(Difficulty.Medium); // 输出:2
      console.log(Difficulty.Hard); // 输出:3
      
    • 外部枚举

      • 外部枚举与普通枚举类似,但它们被定义在单独的文件中,并且可以通过模块系统导入。
      // file: directions.ts
      export enum Direction {
          Up,
          Down,
          Left,
          Right
      }
      ​
      // file: main.ts
      import { Direction } from './directions';
      ​
      console.log(Direction.Up); // 输出:0
      
    • 异构枚举

      • 异构枚举允许你将数字和字符串值混合在一起。
      enum Mixed {
          A = 'A',
          B = 100,
          C = 'C',
          D = 200
      }
      ​
      console.log(Mixed.A); // 输出:"A"
      console.log(Mixed.B); // 输出:100
      console.log(Mixed.C); // 输出:"C"
      console.log(Mixed.D); // 输出:200
      
  • any,unknown,void

    • unknown是any的一个替代类型
    • any允许赋值和被赋值,unknown只允许被赋值
  • never

    • 表示永远不存在值的类型

    • 常在防御性编程中使用

      function text(x: string | number): boolean {
          if (typeof x === "string") {
              return true;
          } else if (typeof x === "number") {
              return false;
          }
          return throwError('参数格式不对');
      }
      ​
      function throwError(message: string): never {
          throw new Error(message);
      }
      
  • 数组类型[]

  • 元组类型 tuple

    • 元组是数组的特殊形式
    • 需要显示地去标注数组的每一个元素的类型

函数类型

  • 定义:TS定义函数类型时要定义输入参数类型和输出类型

  • 输入参数:参数支持可选参数和默认参数

  • 输出参数:输出可以自动推断,没有返回值时,默认为void类型

  • 函数重载:名称相同但参数不同,可以通过重载支持多种类型

    • function add(x: number[]): number
      function add(x: String[]): String
      function add(x: any[]): any {
          if (typeof x[0] === 'number') {
              return x.join()
          }
          if (typeof x[0] === 'string') {
              return x.reduce((acc, cur) => acc + cur)
          }
      }
      
    • 多次定义具有相同名称但参数类型或数量不同的函数会进行函数重载
    • 在连续的函数重载定义之后,需要写一个通用的函数实现,这个实现需要能够处理所有之前定义的特定情况。它通常采用一个较为宽泛的类型定义,以确保能够适应和处理各种不同的输入参数。这样的设计使得函数接口统一,同时能够灵活应对多种数据类型。

interface 接口

  • 定义:接口是为了定义对象类型

    • 对象类型:描述一个具体的JS对象,是对所有键值对的描述
  • 特点:

    • 可选属性 :?
    • 只读属性:readonly
    • 可以描述函数类型
    • 可以描述自定义属性
  • 总结:接口非常灵活 duck typing

  • 定义:写法和JS差不多,增加了一些定义

  • 特点:

    • 增加了public、private、protected 修饰符

      • class Person{
            protected name:string
            private sex:string
            public constructor(name:string){
                this.name = name
                this.sex = 'male'
            }
        }
        class Student extends Person{
            study(){
                console.log(this.name);
                //console.log(this.sex);//属性sex是私有的,只能在Person类中访问
            }
        }
        ​
        let person = new Person('张三');
        //person.name//属性name是受保护的,只能在Person类和子类中访问
        //person.sex//属性sex是私有的,只能在Person类中访问
        
    • 抽象类:

      • 只能被继承,不能被实例化 只能作为父类存在
      • 作为基类,抽象方法必须被子类实现
      • abstract class Animal{
            constructor(name:string){
                this.name = name
            }
            public name:string
            public abstract say():void
        }
        ​
        // 抽象类不能被实例化
        // class Dog extends Animal{
        //     constructor(name:string){
        //         super(name)
        //     }
        // }
        
    • interface约束类,使用implements关键字

TS进阶

高级类型

  • 联合类型:|

    • let num: number | string
      num = 8
      num = 'eight'
      
  • 交叉类型:& 取交集

    • interface Person {
          name: string
          age: number
      }
      type Student = Person & { grade: number }
      ​
      const stu: Student = {
          name: 'John',
          age: 20,
          grade: 90
      }
      
  • 类型断言 告诉编译器某个实例的具体类型

    • 手动指定相关类型,不让编译器进行错误抛出

    • 用法:

      • as 关键字

        //类型“number”上不存在属性“length”
        // function getLength(arg: number | string): number {
        //    return arg.length
        // }function getLength(arg: number | string): number {
            const str = arg as string// 类型断言  断言arg是一个字符串
            if (str.length) {
                return str.length
            } else {
                const num = arg as number// 类型断言  断言arg是一个字符串
                return num.toString().length
            }
        }
        
      • 尖括号语法

        let someValue: any = "this is a string";
        ​
        // 使用尖括号语法断言 someValue 是一个字符串
        let length: number = <string>someValue.length;
        

        它看起来更像类型转换,这在某些情况下可能更直观

  • 类型别名(type)

    • 定义:给类型起个别名

    • type VS interface(接口)

      • 相同点:

        • 都可以定义对象或函数
        • 都允许继承
      • 差异点:

        • interface是TS用来定义对象,type是用来定义别名方便使用
        • type可以定义基本类型,interface不行
        • interface可以合并重复声明,type不行

泛型

泛型允许在函数、接口和类中使用变量作为类型参数。

这意味着可以创建可重用的组件,这些组件可以在不同的类型上以相同的方式工作,而不需要为每个类型编写单独的代码。

基本用法

泛型可以通过在函数、接口或类名前加上尖括号 <> 并指定一个或多个类型参数来使用。这些类型参数在组件内部用作类型变量。

泛型函数
function identity<T>(arg: T): T {
    return arg;
}
​
//使用时两种方法指定类型
//1.定义要使用的类型
//2.通过TS类型推断,自动推导类型
const output = identity<string>("Hello, world!");  // 定义T为string
const output2 = identity(123);  // TS类型推断 自动推断T的类型为numbe
泛型接口
interface GenericIdentityFn<T> {
    (arg: T): T;
}
​
const identityFn: GenericIdentityFn<number> = identity;

这里定义了一个 GenericIdentityFn 接口,它描述了一个接受一个 T 类型的参数并返回 T 类型的函数。然后,你可以使用这个接口来注解一个具体的函数。

泛型类
class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
​
    constructor(zeroValue: T, add: (x: T, y: T) => T) {
        this.zeroValue = zeroValue;
        this.add = add;
    }
}
​
const stringNum = new GenericNumber<string>('', (x, y) => x + y);

在这个例子中,GenericNumber 类是一个泛型类,它有两个属性:zeroValueaddzeroValue 是类的类型参数 T 的一个实例,而 add 是一个接受两个 T 类型参数并返回 T 类型结果的函数。

好处
  1. 类型安全:泛型提供了一种方式,使得函数和组件在操作时能够保持类型的一致性。
  2. 代码重用:泛型允许你编写可以在多种类型上工作的通用代码。
  3. 性能:使用泛型可以避免运行时的类型检查,因为类型检查是在编译时完成的。
应用场景:

定义一个print函数,这个函数的功能是把传入的参数打印出来,再返回这个参数,传入参数的类型是string,函数返回类型为string。支持打印任意类型应该如何写?

function print<T>(arg:T):T{
    console.log(arg);
    return arg
}

泛型的作用是临时占位,之后通过传来的类型进行推导

泛型工具类型
基础操作符
  • typeof:获取类型

    interface Person {
        name: string
        age: number
    }
    const sem: Person = { name: 'sem', age: 20 }
    type Sem = typeof sem;// typeof操作符可以获取一个变量或对象的类型=>type Sem = Person
    
  • keyof:获取所有键

    interface Person {
        name: string
        age: number
    }
     type k1 = keyof Person// type k1 = "name" | "age"
     type k2 = keyof Person[]// type k2 = "length" | "toString" | "pop" | "push" | "concat" | "join"
    
  • in:遍历枚举类型

    type Keys = 'a' | 'b' | 'c'
    type Obj = {
        [p in Keys]: any
    }// type Obj = { a: any; b: any; c: any; }
    
  • T[K]:索引访问

    interface Person {
        name: string
        age: number
    }
    let type1: Person['name']// type1 = string
    let type2: Person['age']// type2 = number
    
  • extends:泛型约束

    从 TypeScript 4.1 开始,你可以使用 extends 关键字来约束泛型参数,这样泛型参数就必须是某个类型或其子类型的实例。

    interface Lengthwise{
        length: number
    }function loggingIdentity<T extends Lengthwise>(arg: T): T {
        console.log(arg.length);
        return arg
    }// loggingIdentity({value:3})// 报错 没有length属性
    loggingIdentity({length:3})
    
常用工具类型
  • Partial:将类型属性变为可选

    type PartialPoint = Partial<{ x: number; y: number }>;
    ​
    // 以下都是合法的 PartialPoint 类型
    const point1: PartialPoint = { x: 1 }; // y 是可选的
    const point2: PartialPoint = { y: 2 }; // x 是可选的
    const point3: PartialPoint = {}; // 所有属性都是可选的
    
  • Required:将类型属性变为必选

    type PartialPoint = { x?: number; y?: number };
    ​
    type RequiredPoint = Required<PartialPoint>;
    ​
    // 以下都是合法的 RequiredPoint 类型
    const point1: RequiredPoint = { x: 1, y: 2 }; // x 和 y 都是必选的
    // const point2: RequiredPoint = { x: 1 }; // 错误,y 是必选的
    // const point3: RequiredPoint = {}; // 错误,x 和 y 都是必选的
    
  • Readonly:将类型属性变为只读

    type Point = { x: number; y: number };
    ​
    type ReadonlyPoint = Readonly<Point>;
    ​
    // 以下都是合法的操作
    const point: ReadonlyPoint = { x: 1, y: 2 };
    console.log(point.x); // 合法
    console.log(point.y); // 合法
    ​
    // 以下都是非法的操作,因为属性是只读的
    // point.x = 3; // 错误,属性是只读的
    // point.y = 4; // 错误,属性是只读的
    
  • Pick、Record ...

TypeScript实战

声明文件

  • declare:三方库需要类型声明文件
  • .d.ts:声明文件定义
  • @types:三方库TS类型包
  • tsconfig.json:定义TS的配置

泛型约束后端接口类型

import axios from 'axios'interface API {
    '/book/detail': {
        id: number,
    },
    '/book/comment': {
        id: number
        comment: string
    }
}
​
function request<T extends keyof API>(url: T, obj: API[T]) {
    return axios.post(url, obj)
}
​
//case1:路径错误
// request('/book/test', {
//  id: 1,
//  comment:‘非常棒!’
// })//case2:参数错误
// request('/book/detail', {
//  id: 1,
//  comment: 2
// })

避免TypeScript沦为AnyScript,有什么好的方式

引入TS项目分成两类

  • 存量项目的TS改造

    • 逐渐把定义any类型的补充相关类型定义,完善这个类型定义的精确度和完整性
  • 新增项目

    • 一开始就做类型定义,规范处理