我的TS学习汇总

612 阅读17分钟

经过一个礼拜的学习,我也是把ts的一些基本内容都学完了。这篇主要是我对ts学习的一个汇总,如果有需要可以看我的 《初始TS》系列,这个系列主要是通过官方文档输出的。

初始TS系列

初识TS(一)
初识TS(二)

一、TS 基础

  1. TS 是什么,有什么用?

    • 是什么:TypeScript 是 JavaScript 的超集,在 JavaScript 的基础上增加了静态类型审查
    • 有什么用:
      • 增加代码的维护性:比如某个变量原本是字符串,但是中间可能会被不经意改变成其他类型,导致报错
      • 提高可读性
      • 可以提前发现错误,tsc 编译器会对代码进行静态分析,不符合的会直接在写代码的时候报错

 

例子:

  let obj = {
    name: "张三"age: 10
  }
  console.log(obj.sex)

按道理不能访问不存在的属性,在原生 js 里可以,并且会把他编译成 undefined,在 ts 里他就会提示错误

  1. TS 怎么运行

  先要下载 typescript 编译器

npm install -g typescript

 

使用 tsc <filename>.ts 把 ts 编译成 js 文件后在 node 环境中运行

也可以使用监听 tsc <filename>.ts -w 保存文件就会自动编译

 

二、tsconfig.json 文件

 

tsconfig.json 文件是 TypeScript 项目的配置文件。通过它可以指定编译器的选项,从而自定义 TypeScript 编译的行为。

 

  1. 文件位置

该文件放在项目的根目录,主要供 tsc 编译器使用。

  1. 生成指令

可以通过运行下面的指令生成 tsconfig.json 文件:

tsc --init
  1. 常见配置

 

  • 3.1 compilerOptions(编译器选项)

这是 tsconfig.json 文件中最常见的选项之一,通过它可以指定编译器的行为,常见的编译器选项包括:

 

    • target(目标代码的 ECMAScript 版本) :指定编译后的 JavaScript 代码要符合的 ECMAScript 版本。
    • module(模块系统):指定 TypeScript 使用的模块化 ,如:CommonJSAMDES2015 等。
    • outDir(输出目录):指定编译后的 JavaScript 文件的输出目录。
    • sourceMap(源码映射):指定是否生成源码映射文件,以便在调试时能够方便地追踪 TypeScript 代码。
    • strict(严格模式 :指定是否启用 TypeScript 的严格模式,以应用更严格的类型检查规则。
    • baseUrl 和 paths(模块解析):用于配置 TypeScript 的模块解析规则,可以指定模块的基础路径和路径别名。

 

  • 3.2 include 和 exclude(包含和排除文件)

这两个选项用于指定要包含和排除的文件。在 include 中可以使用通配符模式来匹配文件,而在 exclude 中可以指定要排除的文件或文件夹。

 

  • 3.3 references(引用其他项目)

这个选项用于指定要引用的其他 TypeScript 项目。通过引用其他项目,可以在一个项目中使用另一个项目的类型定义文件。

 

  • 3.4 files(文件列表)

这个选项用于手动指定要编译的文件列表。如果指定了 files,则只会对这些文件进行编译,而不会考虑 includeexclude

三、基本数据类型的声明

基本数据类型的声明一般格式

var/let/const <name>:type = value


number: let num:number = 2
string: let str:string = "2"
boolean: let flag:boolean = false
undefined: let un:undefined = undefined
null: let nu:null = null
symbol: let sym:symbol = Symbol("1")

 

四、引用类型声明

  1. 数组和元组

  • 数组

ts 规定数组中的元素类型要相同

  • 语法:
let arr:类型[] = []
或者使用泛型 
let arr:Array<类型> = []
  • 用法:
//声明直接赋值 
let arr:string[]=["a","b"] 
//先声明再赋值 
let arr2:number[] 
let arr3:boolean[] arr2=[1,2,3] 
//后续添加的元素也必须符合数组的类型 
arr.push("8") 
//⼆维数组 
let arr4:string[][]=[["a","b"],["e","f"]] 
//对象数组 
let arr:{name:string,age:number}[]=
[
  {name:"aa",age:18},
  {name:"bb",age:17}
] 
//函数数组 
let arr: (()=>number)[]=[function(){
  return 123
},function(){
  return 443
}] 
//定义包含多个数据类型的数组 
let arr:(string|number|(()=>void))[]
  =
  ["hh",123,() => {console.log(1)}]
  • 元组

    • 元组就是元素可以不同类型的数组
    • 如:
let arr:[string,number,boolean]=["A",123,true] 
//先声明再赋值 
let arr2:[boolean,string] 
arr2[0]=true; arr2[1]="123" 
//如果后续通过任何⽅法向数组添加数据,也可以,后添加的数据是前⾯每个数据的联合类型 
arr2.push("123")
  1. 对象

给对象限定类型要给每个属性都限定好

    • 基本语法:
const obj:{name:string,age:number}
    • 可选属性:

属性名后⾯加问号,代表该属性可有可⽆,⼀个对象中可以有任意多个可选属性

const obj:{name:string,age:number,job?:string} = {
  name:"zhangsan",
  age:12
} // 可以没有 job 属性
    • 任意属性:

假设我希望 obj 里必须要有 name 和 age 属性,别的可以随意

const obj:{name:string,age:number,
  [propsName:string]:string|number|boolean
 } = {
  name: "张三"age: 18,
  job: "前端",
  isMarried: true
}
//propName 代表属性名,肯定是字符串,propsName 只是形参,可以换成别的名字 
//任意属性只有⼀个没有多个,它代表了其他的所有属性 
//任意属性的类型⼀定是其他类型(包含可选属性)的⽗类
    • 只读属性:

在对象属性的前⾯加上 readonly,代表该属性只能访问,不能修改,⼀个对象可以有任意多个只读属性

const obj: {
  readonly name:string,
  age:number
} = {
  name: "张三"age: 18
}
// 如果要修改 name 属性会报错
obj.name="里斯" //报错
  1. 函数

五、类型别名和接口

  1. 类型别名 type

    • 基本语法和使用

      • 作用
        • 就在 TypeScript 中,类型别名是给现有类型取⼀个新的名字。它可以⽤于提⾼代码的可读性和可维护性,以及 减少重复的类型定义。
      • 简单类型别名
      type myNum = number
      let num:myNum = 1
      • 复杂类型别名
      // 对象
      type Person = {
          name: string;
          age: number;
      };
      let person: Person = { name: "Alice", age: 30 };
      // 函数
      type MathFunction = (x: number, y: number) => number;
      let add: MathFunction = (a, b) => a + b;
    • 结合其他类型

      • 可以结合联合类型、元组来使用
  type Girl= {
    name:string;
    age:number;
  }
  type Boy = {
    hobby:sring[];
    height:number;
  }
  type XM = Girl | Boy
  
  type Arr = [number, string, boolean]
  1. 接口 interface

    • 基本作用

      • 在 TypeScript 中,我们使⽤接⼝(Interfaces)来定义对象的类型。
      • 我们之前定义对象类型都是 形如{name:string,age:number}这种形式 但是如果我的别的对象也是这种结构,我们不⾄于每个对象都重新声明⼀遍这个类型吧,所以就需要⽤到接口
    • 基本语法和使用
interface Person{  
  name:string,  
  age:number,  
  salary:number 
} 
let obj:Person={name:"张三",age:18,salary:3500
    • 接口的继承

如果你需要创建⼀个新的接⼝,⽽这个新的接⼝中的部分内容我已经在已存在的接⼝中定义过了,那么可以直接继承,⽆需重复定义

只要存在接⼝的继承,那么我们实现接⼝的对象必须同时实现该接⼝以及他所继承的接⼝的所有属性

interface Person{  
  name:string,  
  age:number,  
  address:string 
} 
interface Girl extends Person{  
  height:number,  
  hobby:string[] 
} 
  
interface Boy extends Person{  
    salary:number,  
    car:string 
} 
let xf:Girl={  
  name:"⼩芳",  
  age:18,  
  address:"北京",  
  height:170,  
  hobby:["逛街","买买买"] 
}


// ⼀个接⼝可以被多个接⼝继承,同样,⼀个接⼝也可以继承多个接⼝,多个接⼝⽤逗号隔开 继承多个接⼝,必须同时实现继承每⼀个接⼝定义的属性
interface IPersonInfo {
    name: string;
    age: number;
    address: string;
}


interface IProfessionalInfo {
    phone: string;
    coat: string;
}


// Girl 接口继承了 IPersonInfo 和 IProfessionalInfo
interface IGirl extends IPersonInfo, IProfessionalInfo {
    height: number;
    hobby: string[];
}


interface IBoy extends IPersonInfo {
    salary: number;
    car: string;
}


// 创建符合 IGirl 接口的对象 xf
let xf: IGirl = {
    name: "⼩芳",
    age: 18,
    address: "北京",
    height: 170,
    hobby: ["逛街", "买买买"],
    phone: "华为",
    coat: "安踏"
};


// 多层继承


interface Person{  
  name:string,  
  age:number,  
  address:string 
} 
interface Girl extends Person{
  height:number,  
  hobby:string[]
} 
interface Xh extends Girl{  
  hair:string 
} 
let xh:Xh={
  hair:"红⾊",
  height:170,
  hobby:["买买买"],
  name:"⼩红",
  age:18,
  address:"北京"
} 
    • 接口同名合并

名字相同的接⼝不会冲突,⽽是会合并为⼀个

interface Person {
  name:string
}
interface Person {
  age:number
}
const xm:Person = {
  name: "小名",
  age: 18
}
  1. 两者的区别

interface 与 type 的区别有下⾯⼏点。

      • (1) type 能够表示⾮对象类型,⽽ interface 只能表示对象类型(包括数组、函数等)。
      • (2) interface 可以继承其他类型,type 不⽀持继承。
      • (3)同名 interface 会⾃动合并,同名 type 则会报错
      • (4) interface 不能包含属性映射(mapping),type 可以
    • 举例
interface Point { 
  x: number; 
  y: number; 
} // 正确 
type PointCopy1 = { 
  [Key in keyof Point]: Point[Key]; 
}; // 报错 
interface PointCopy2 { 
  [Key in keyof Point]: Point[Key]; 
};

六、其他类型

  1. any

any 表示的是任意类型,可以任意赋值,⼀个变量设置类型为 any,相当于对该变量关闭了类型检测 如果声明⼀个变量不设置类型,其实也是 any 类型。所以在开发中不建议使⽤该⽅法

  1. unknown

unknown 相对于 any 是安全的

要想正常使⽤ unknown,必须保证操作是合法的

let num:unknown = 5


console.log(num*2)// 报错
//必须使用 typeof 类型保护,必须自己确认类型才能使用
if(typeof num == "number") {
  console.log(num*2)
}
  1. never

never 表示永远不会有返回值的类型,⽐如抛出异常,或者死循环,那么该函数永远执⾏不完,不可能有返回值 比如:

function fn() {
  throw new Error("error")
  console.log('1')
}
// 或者
function fn() {
  while(1) {
    
  }
  console.log(1)
}
  1. void

用来给函数定义 没有返回值

function fn():void {
  console.log("123")
}
  1. 值类型

在 TS 中,单独的值也是一个类型,声明了值类型后,那么这个变量就只能是这个值

let num:123 = 123
let str:"123" = "123"

七、联合类型

联合类型(Union Types)表示取值可以为多种类型中的⼀种

let a:number | string = 123
a = "123" // 没报错


// 定义不同类型数组
const arr:(string | number)[] // 元素的类型可以是字符串,也可以是数字
arr = [1, "123"]

当如果访问联合类型的属性和方法,TS 不确定类型的时候,一般只能访问联合类型共有的方法和属性

function fn(a:number | string) {
  // a.substring() 报错
  a.toString() // OK
} 


一般来说,我们可以使用 typeof,或者使用类型缩小

八、typeof 和 keyof

  1. typeof

JavaScript ⾥⾯,typeof 运算符只可能返回⼋种结果,⽽且都是字符串

typeof undefined; // "undefined" 
typeof true; // "boolean" 
typeof 1337; // "number" 
typeof "foo"; // "string" 
typeof {}; // "object" 
typeof parseInt; // "function" 
typeof Symbol(); // "symbol" 
typeof 127n // "bigint"

TypeScript 将 typeof 运算符移植到了类型运算,它的操作数依然是⼀个值,但是返回的不是字符串,⽽是该值的 TypeScript 类型

let num = 123
type myNum = typeof num // myNum = number


// ts 中的 typeof 是 根据已有的值 来获取值的类型 来简化代码的书写
const obj = {x: "123"}
type MyObj = typeof obj //MyObj = {x: string}
type str = typeof obj.x //MyObj = string
  1. keyof

keyof 运算符接受⼀个对象类型作为参数,返回该对象的所有键名组成(值类型)的联合类型。keyof 类型别名

type Person={name:string,age:number} 
type MyType=keyof Person //name|age 
//type MyType=keyof {name:string,age:number} //name|age 
let a:MyType="name" // 值只能是 name 或者 age

九、交叉类型

交叉类型(Intersection Types)⽤于组合多个类型,⽣成⼀个包含所有类型特性的新类型。可以 理解为将多个类型合并为⼀个更⼤的类型,新类型拥有所有原始类型的成员。使⽤ & 符号表示交叉类型。

type Person={
  name:string,
  age:number
} 
type Emp={
  salary:number,
  address:string
} 
type C=Person&Emp&{
  height:number
} 
let obj:C={
  name:"张三",
  age:18,
  salary:3500,
  address:"beijing",
  height:180
} 

十、类型断言

类型断⾔就是我明确的知道我这个数据肯定是字符串,告诉编译器你不⽤检测他了。

as 类型
//或者 
<类型>值

我们可以将联合类型断言成其中的一个类型

// type C=string|number 
function fn(m:string|number){  
  (m as string).substring(1) 
} 
fn(100)//错误

注意:类型断⾔只能欺骗 ts 编译器,让他不报错,⽆法避免项⽬运⾏时的错误,所以使⽤断⾔要谨慎

十一、枚举类型

 

1. 枚举类型的基本使用和默认值规则

// 订单状态枚举,展示基本的数字枚举,体现默认值递增规则
enum OrderStatus {
    Start, // 默认值为 0
    Unpaid, // 1
    Shipping, // 2
    Shipped, // 3
    Complete // 4
}

 

2. 手动赋值

 

// 手动赋值的枚举示例,可用于有特殊数字要求的场景
enum Days {
    Sun = 7,
    Mon = 1,
    Tue, // 2
    Wed, // 3
    Thu, // 4
    Fri, // 5
    Sat // 6
};


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

 

3. 使用计算值

 

// 计算值示例,使用常量或计算值后,后续枚举成员需手动赋值
const Start = 1;
enum Index {
    a = Start,
    b = 2, // 这里不能省略赋值,否则报错
    c = 3
}

 

4. 反向映射(数字枚举)

 

// 数字枚举反向映射示例
enum Status {
    Success = 200,
    NotFound = 404,
    Error = 500
}


console.log(Status["Success"]); // 200
console.log(Status[200]); // 'Success'
console.log(Status[Status["Success"]]); // 'Success'

 

5. 字符串枚举

 

// 字符串枚举示例,每个成员值必须是字符串字面量或同一枚举中的其他字符串成员
enum Status2 {
    Success = "200",
    NotFound = "404",
    Error = "500"
}


console.log(Status2["Success"]); // "200"
// 错误示例,使用函数返回值作为枚举成员初始值(对于字符串枚举,不能这样做)
const getValue = () => {
    return "0";
};
enum ErrorIndex {
    a = getValue(), // 报错
    b, 
    c
}

 

十二、泛型

1、泛型概述

泛型是一种编程特性,它允许在定义函数、接口或类时不指定具体类型,在使用时再确定,从而提高代码复用性和可维护性,增强类型安全性。

 

2、函数中使用泛型

(一)简单泛型函数

// 此函数接受任意类型 T 的参数 arg,并返回相同类型的值,实现了类型参数化。
function identity<T>(arg: T): T {
    return arg;
}
// 显式指定类型调用,这里指定 T 为 Number 类型。
identity<Number>(100); 
// 编译器自动推断类型调用,根据传入值推断 T 类型。
identity(100); 

 

(二)多个类型参数的泛型函数

// 该函数有两个类型参数 T 和 U,接受不同类型参数并返回其中一个类型的值,用于处理多种类型关联的情况。
function identity<T, U>(arg: T, arg2: U): T {
    return arg;
}
// 显式指定 T 为 Number,U 为 String 类型来调用函数。
identity<Number, String>(4, "hello");

 

 

(三)类型推断可能导致错误及解决

// 此函数期望两个参数数组类型相同,都是 T 类型数组,但编译器可能因参数类型不一致无法正确推断。
function fn2<T>(arr1: T[], arr2: T[]): T[] {
    return arr1.concat(arr2);
}
// 错误调用,因为数组元素类型不同,编译器不知如何确定 T 类型。
// fn2([1,2],["a","b"]); 
// 正确调用,指定 T 为 number 和 string 的联合类型,解决类型不一致问题。
fn2<number | string>([1, 2], ["a", "b"]); 

 

 

3、接口中使用泛型

// 此泛型接口有类型参数 N,用于指定 age 属性类型,使接口可适应不同类型的 age 值。
interface Person<N> {
    name: string;
    age: N;
}
// 创建一个 Person 接口实例,指定 N 为 string 类型,age 值必须是字符串。
const xiong: Person<string> = {
    name: "xiong",
    age: "18"
    // 若 age 为数字,如 18,会报错,因与指定的 string 类型不符。
};

 

4、类中使用泛型

// 泛型类 Person,类型参数 T 用于确定 name 属性类型,使类可创建不同类型 name 的实例。
class Person<T> {
    name: T;
    age: number;
    constructor(name: T, age: number) {
        this.name = name;
        this.age = age;
    }
}
// 创建 Person 类实例,指定 T 为 string 类型。
const xiong = new Person<string>('xiong', 18);
console.log(xiong.name, xiong.age); 

 

5、类型别名中使用泛型

// 定义泛型类型别名 C,类型参数 T 决定 value 属性类型,可创建不同类型值的对象。
type C<T> = { value: T };
// 创建 C 类型实例,指定 T 为 string 类型。
let obj: C<string> = { value: "hello" };

 

 

6、类型参数默认值

(一)函数中的类型参数默认值

// 泛型函数 fn,类型参数 T 默认值为 string,可接受指定或默认类型参数的调用。
function fn<T = string>(m: T) {
    return m;
}
// 调用时传入数字,编译器根据值推断 T 类型,覆盖默认的 string 类型。
fn(123); 

 

 

(二)类中的类型参数默认值

// 泛型类 Person,类型参数 T 默认值为 string,类内方法和属性依赖此类型。
class Person<T = string> {
    list: T[] = [];
    add(t: T) {
        this.list.push(t);
    }
}
// 创建实例,未指定类型时使用默认值 string。
let xm = new Person();
// 添加 string 类型值正确。
xm.add("4"); 
// 添加数字类型值报错,因为实例的 T 类型为 string。
xm.add(4); 

 

 

7、泛型约束

(一)基本泛型约束示例

// 泛型函数 fn,约束类型参数 T 必须是 Person 类型或其子类型,确保参数符合特定类型要求。
function fn<T extends Person>(person: T) {}
// 传入满足约束条件的对象,这里 T 被推断为包含 name、age 和 salary 属性的类型。
fn<{ name: string, age: number, salary: number }>({ name: "张三", age: 18, salary: 3500 });

 

 

8、泛型的嵌套

interface Box<T> {
    item: T;
}
interface Person<T> {
    name: T;
}
// 这里创建了一个 Box 类型实例,其 item 是 Person 类型,Person 的 name 是 string 类型,展示了泛型嵌套的复杂性和灵活性。
let obj: Box<Person<string>> = {
    item: {
        name: "hello"
    }
};


// 类似地,创建另一个 Box 类型实例,其 item 也是 Box 类型,内部 Box 的 item 是 string 类型。
let anotherObj: Box<Box<string>> = {
    item: {
        item: "hello"
    }
};

十三、class 类

js 中的 class 类

class Parent {  
  constructor(x) {  
    this.x = x;  
    this.sayHello = function () {  
      console.log("sayHello")
    }  
  }  
  getX() {  
    console.log("getX==>", this.x)  
  }  
}

ts 中的 class 类

//ts 中 class 类构造函数⾥⽤到的所有属性,必须提前定义类型 
class Person{  
  //实例属性  
  name:string="张三";//可以在定义的时候直接赋初始值  
  age:number  
  constructor(name,age){  
    this.name=name;  
    this.age=age  
  }  
  //类属性(静态属性) 只能通过类名访问和修改,对象实例访问不到  
  static count:number=100  
  //只读属性  
  readonly sex:string="boy"  
  eat(){  
    console.log("我在吃饭")  
  }  
  //静态⽅法  
  static sleep(){  
    console.log("我睡觉呢")
  } 
}

1、 访问修饰符

TypeScript 可以使⽤三种访问修饰符(Access Modifiers),分别是 public、private 和 protected。

  • public 修饰的属性或⽅法是公有的,可以在任何地⽅被访问到,默认所有的属性和⽅法都是 public 的
  • private 修饰的属性或⽅法是私有的,不能在声明它的类的外部访问
  • protected 修饰的属性或⽅法是受保护的,它和 private 类似,区别是它在当前类和⼦类中也是允许被访问的

2、抽象方法和抽象类

抽象⽅法:就是没有⽅法体的⽅法

抽象⽅法只能出现在抽象类中 ,包含了抽象⽅法的类,就⼀定是抽象类

抽象类中的抽象⽅法必须被⼦类(可以是间接⼦类)实现的。

抽象类中不⼀定包含抽象⽅法

3、implement 关键字

在 TypeScript 中,implements 关键字⽤于检查⼀个类是否遵循特定的接⼝。接⼝(Interface)在 TypeScript 中是 ⼀个⾮常强⼤的⼯具,它描述了⼀组⽅法和属性的形状(Shape),但没有实现它们。

⼀个类可以同时实现多个接口

interface Person{  
  id:number,  
  name:string, 
  play:()=>void 
} 
interface Person1{  
  gender:boolean 
} 
class XiaoMing implements Person,Person1{  
  id:number  
  name:string  
  play:()=>void  
  gender:boolean  
  age:number 
  constructor(a:number,b:string,c:()=>void){  
      this.id=a;  
      this.name=b;  
      this.play  
  } 
}

十四、类型推导

如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出⼀个类型。

以下代码虽然没有指定类型,但是会在编译的时候报错:

let a = 1
a = '1' //报错

如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型⽽完全不被类型检查,函数的参数也是这样:

let a
a = 1 // ok
a = '1' // ok
function fn(a, b) {
  return a+b
} // a:any,b:any

十五、TS 模块化

ts 模块化是建⽴在 es6 模块化的基础上,与 JS 中的写法有许多的不同

任何包含 import 或 export 语句的⽂件,就是⼀个模块(module)。相应地,如果⽂件不包含 export 语 句,就是⼀个全局的脚本⽂件。

 

模块本身就是⼀个作⽤域,不属于全局作⽤域。模块内部的变量、函数、类只在内部可⻅,对于模块外部是 不可⻅的。暴露给外部的接⼝,必须⽤ export 命令声明;如果其他⽂件要使⽤模块的接⼝,必须⽤ import 命令来输⼊。

 

如果⼀个⽂件不包含 export 语句,但是希望把它当作⼀个模块(即内部变量对外不可⻅),可以在脚本头部 添加⼀⾏语句。

export {};

  1. 类型的导入导出

  • 导出

一般导出复杂属性类型

interface Person2{  name:string,  age:number } 
type obj={gender:boolean,like:string[]} 
export type {obj,Person2} //⽅式1 
export {obj,Person2} //⽅式2 
export {type obj,type Person2}//⽅式3 

导出或者引⼊的时候加⼊type关键字的好处

1.减少编译后输出⽂件的⼤⼩

2.避免不必要的运⾏开销

3.明确表达意图,可读性

  • 导入
import type { obj,Person2,Person } from "./a";//⽅式1 
import { obj,Person2,Person } from "./a";//⽅式2 
import {type obj,type Person2,type Person } from "./a";//⽅式1
  1. 接口的默认导出

export default interface Person{  
  name:string,  
  age:number 
} 
export interface Person{  
  name:string,  
  age:number 
} 
//以上两种写法都是对的