深入浅出Typescript| 豆包MarsCode AI刷题

115 阅读10分钟

青训营伴学笔记第四篇

首先明白为什么要学TS?TS和JS的区别是啥?

TypeScript(TS)

  • 定义:JavaScript 的超集,用于解决大型项目的代码复杂性。
  • 类型系统:强类型,支持静态和动态类型。
  • 错误检查:可以在编译期间发现并纠正错误。
  • 变量类型:不允许改变变量的数据类型。

JavaScript(JS)

  • 定义:一种脚本语言,用于创建动态网页。
  • 类型系统:动态弱类型语言。
  • 错误检查:只能在运行时发现错误。
  • 变量类型:变量可以被赋值成不同类型。 总的来说,TypeScript 在大型项目开发中,通过其强类型系统和编译时错误检查,能够更好地管理代码复杂性和提高代码质量;而 JavaScript 则更适合快速开发和小型项目,具有更高的灵活性。

TS带来的优势

  1. 类型安全:通过盾牌图标来表示,意味着 TypeScript 能够在编译时检查类型错误,避免在运行时出现类型相关的问题,提高代码的可靠性。
  2. 下一代 JS 特性:用 “JS” 图标表示,TypeScript 包含了 ECMAScript 的最新特性,并且可以在不支持这些特性的环境中进行编译和运行。
  3. 完善的工具链:通过扳手和螺丝刀的图标表示,TypeScript 有强大的开发工具支持,如智能感知、自动补全和重构工具等,能够提高开发效率。

TS不仅仅是一门编程语言,更是一个生产工具可见TS是能够提高开发效率的工具

TS基础

基础类型
  1. 基本类型
  • boolean, number, string:这些是 TypeScript 中的基本数据类型,分别表示布尔值、数字和字符串。

  • undefined, null:这两个类型表示变量没有值。undefined通常表示变量已声明但未赋值,null则表示变量被故意设置为空值。

  • any, unknown, void

    • any:这个类型可以表示任意类型的值,使用时会失去类型检查的优势。
    • unknown:类似于any,但在使用前必须进行类型检查或断言。
    • void:通常用于函数没有返回值的情况。
  • never:这个类型表示永远不会有值的情况,例如函数总是抛出异常或进入无限循环。

  1. 数组类型

    • 数组类型 [] :表示一个可以存储多个值的容器,这些值的类型可以相同也可以不同(在使用any类型时)。
  2. 元组类型

    • 元组类型 tuple:元组是一种特殊的数组类型,它允许存储不同类型的值,但每个位置的类型是固定的。

右侧的代码示例展示了如何在函数中使用类型断言和错误处理:

  • 第一个函数test接受一个参数x,类型为string | number,并根据x的类型返回不同的值。如果xstring,返回true;如果xnumber,返回false;否则抛出错误。
  • 第二个函数throwError接受一个string类型的参数message,并总是抛出一个带有messageError

这些内容对于理解 TypeScript 中的类型系统和如何在代码中进行类型检查和处理非常重要。

函数类型
  1. 函数类型定义

    • 在 TypeScript 中定义函数类型时,需要定义输入参数类型和输出类型。
  2. 输入参数

    • 参数支持可选参数和默认参数。
  3. 输出参数

    • 输出可以自动推断,没有返回值时,默认为void类型。
  4. 函数重载

    • 名称相同但参数不同的函数,可以通过重载支持多种类型。
interface接口
  1. 定义

    • 接口是为了定义对象类型。
  2. 特点

    • 可选属性:使用?来表示。
    • 只读属性:使用readonly来表示。
    • 可以描述函数类型。
    • 可以描述自定义属性。
  3. 总结

    • 接口非常灵活,具有 “duck typing”(鸭子类型)的特性。
  1. 定义

    • 写法和 JS 差不多,增加了一些定义。
  2. 特点

    • 修饰符

      • 增加了publicprivateprotected修饰符。
    • 抽象类

      • 只能被继承,不能被实例化。
      • 作为基类,抽象方法必须被子类实现。
    • interface 约束类

      • 使用implements关键字。

TS进阶

1. 联合类型(Union Types)
  • 示例代码
let num: number | string;
num = 8;
num = 'eight';
  • 解释:联合类型允许一个变量具有多种类型之一。在这个例子中,num变量可以是number类型或者string类型。
2. 交叉类型(Intersection Types)
  • 示例代码
interface Person {
  name: string;
  age: number;
}
type Student = Person & { grade: number };
const stu: Student = {
  name: 'lin',
  age: 18,
  grade: 90
};
  • 解释:交叉类型结合了多个类型的所有成员。在这个例子中,Student类型继承了Person类型的所有属性,并且添加了grade属性。
3. 类型断言(Type Assertion)
  • 示例代码
function getLength(arg: number | string): number {
  return arg.length;
}
  • 解释:类型断言允许你手动指定一个值的类型。在这个例子中,函数getLength接受numberstring类型的参数,并返回其长度(假设number类型的参数已经被正确处理)。
4. 类型别名(Type Alias vs Interface)
  • 定义:类型别名是给类型起一个新名字。

  • 相同点

    1. 都可以定义对象或函数。
    2. 都允许继承。
  • 差异

    1. interface是 TS 用来定义对象,type是用来定义别名方便使用。
    2. type可以定义基本类型,interface不行。
    3. interface可以合并重复声明,type不行。
  • 示例代码

// 类型别名
type Person = {
  name: string;
  age: number
};
// 接口
interface Person {
  name: string;
  age: number
};
TS进阶 —— 泛型
基础使用

基本定义

  • 泛型的语法是<>里面写类型参数,一般用T表示。

  • 使用时有两种方法指定类型:

    1. 定义要使用的类型。
    2. 通过 TS 类型推断,自动推导类型。
  • 泛型的作用是临时占位,之后通过传来的类型进行推导。

代码示例

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

print<string>('hello'); // 定义T为string
print('hello'); // TS类型推断,自动推导类型为string
  • 定义了一个泛型函数print<T>,它接受一个类型为T的参数arg,并返回arg

  • 给出了两种调用方式:

    1. 显式指定类型为stringprint<string>('hello')
    2. 让 TS 自动推断类型:print('hello')
泛型工具类型 —— 基础操作符
  1. typeof:获取类型

    • 示例代码:
interface Person {
  name: string;
  age: number;
}
type K1 = keyof Person; // "name" | "age"
type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join"
  • 解释:typeof 操作符用于获取变量或表达式的类型。在这个示例中,keyof 操作符用于获取接口 Person 及其数组类型的所有键。
  1. keyof:获取所有键

    • 示例代码:
type Keys = "a" | "b" | "c";
type Obj = {
  [p in Keys]: any;
}; // --> { a: any, b: any, c: any }
  • 解释:keyof 操作符用于获取类型的所有键。在这个示例中,Keys 是一个联合类型,Obj 是一个对象类型,其键是 Keys 中的每个成员。
  1. in:遍历枚举类型

    • 示例代码:
interface IPerson {
  name: string;
  age: number;
}
let type1: IPerson['name']; // string
let type2: IPerson['age']; // number
  • 解释:in 操作符用于遍历枚举类型。在这个示例中,IPerson 是一个接口,type1 和 type2 通过索引访问获取 IPerson 中的特定类型。
  1. T [K]:索引访问

    • 示例代码:
interface Lengthwise {
  length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}
loggingIdentity({value: 31});
loggingIdentity({length: 5});
  • 解释:T[K] 操作符用于索引访问。在这个示例中,loggingIdentity 函数接受一个泛型参数 T,并要求 T 必须包含 length 属性。函数内部访问了 arg.length
  1. extends:泛型约束

    • 示例代码:
interface Lengthwise {
  length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}
loggingIdentity({value: 31});
loggingIdentity({length: 5});
  • 解释:extends 操作符用于泛型约束。在这个示例中,loggingIdentity 函数的泛型参数 T 被约束为必须实现 Lengthwise 接口,确保传入的参数有 length 属性。

这些示例展示了如何在 TypeScript 中使用泛型工具类型进行类型操作和约束。

泛型工具类型 —— 常用工具类型
  1. Partial :这个类型工具可以将类型属性变为可选。示例代码如下:
type Partial<T> = {
    [P in keyof T]?: T[P];
};

这里使用了映射类型(Mapped Types),keyof T获取了类型T的所有键,[P in keyof T]?: T[P];表示将T中的每个属性P都变为可选(通过添加?)。

  1. Required :这个类型工具可以将类型属性变为必选。示例代码如下:
type Required<T> = {
    [P in keyof T]-?: T[P];
};

这里使用了-?操作符,表示移除属性的可选性,从而使所有属性变为必选。

  1. Readonly :这个类型工具可以将类型属性变为只读。示例代码如下:
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

这里使用了readonly关键字,表示将T中的每个属性P都变为只读。

这些工具类型在 TypeScript 中常用于处理对象类型的属性,使得在编写代码时可以更灵活地控制类型的可选性、必选性和只读性。

TS实战

声明文件
  1. declare:三方库需要类型声明文件。在 TypeScript 中,当使用第三方库时,可能需要声明文件来告诉编译器这些库中的变量、函数和类的类型。
  2. .d.ts:声明文件定义。这是 TypeScript 中的类型声明文件,通常以.d.ts 为扩展名,用于为 JavaScript 库提供类型信息。
  3. @types:三方库 TS 类型包。这是一个用于存放 TypeScript 类型定义文件的 npm 包,通常用于为第三方库提供类型定义。
  4. tsconfig.json:定义 TS 的配置。这是 TypeScript 项目的配置文件,用于指定编译器选项、文件包含和排除等项目设置。
泛型约束后端接口类型
案例 1:路径错误
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);
}

// 正确调用
request('/book/detail', {
  id: 1
});
  1. 代码分析

    • 首先定义了一个名为API的接口,其中包含两个路径/book/detail/book/comment,并对它们的参数类型进行了约束。
    • id参数的类型被约束为numbercomment参数的类型被约束为string
    • 然后定义了一个名为request的函数,它接受一个 URL 和一个对象作为参数,并使用axios.post发送 POST 请求。
    • 在示例调用中,路径被错误地写成了/book/test,而不是/book/detail/book/comment
  2. 错误原因

    • 错误信息是Argument of type '/book/test' is not assignable to parameter of type 'keyof API'.,这意味着传递给request函数的路径/book/test不在API接口定义的路径中。
  3. 解决方法

    • 修正路径,将/book/test改为/book/detail/book/comment
案例 2:参数错误
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);
}

// 正确调用
request('/book/detail', {
  id: 1
});

request('/book/comment', {
  id: 1,
  comment: '非常棒!'
});
  1. 代码分析

    • 同样使用了API接口定义的路径和参数类型。
    • 在示例调用中,/book/detail路径下的id参数类型被错误地设置为string,而不是number
  2. 错误原因

    • 错误信息是Type 'string' is not assignable to type 'number'.,这意味着传递给/book/detail路径的id参数类型不匹配。
  3. 解决方法

    • 修正参数类型,将id参数的类型从string改为number
总结

这两个案例展示了在使用 TypeScript 进行后端接口类型约束时,如何通过接口定义和泛型来确保参数类型的正确性。错误主要是由于路径和参数类型不匹配导致的,解决方法是修正路径和参数类型,使其符合接口定义。