青训营伴学笔记第四篇
首先明白为什么要学TS?TS和JS的区别是啥?
TypeScript(TS)
- 定义:JavaScript 的超集,用于解决大型项目的代码复杂性。
- 类型系统:强类型,支持静态和动态类型。
- 错误检查:可以在编译期间发现并纠正错误。
- 变量类型:不允许改变变量的数据类型。
JavaScript(JS)
- 定义:一种脚本语言,用于创建动态网页。
- 类型系统:动态弱类型语言。
- 错误检查:只能在运行时发现错误。
- 变量类型:变量可以被赋值成不同类型。 总的来说,TypeScript 在大型项目开发中,通过其强类型系统和编译时错误检查,能够更好地管理代码复杂性和提高代码质量;而 JavaScript 则更适合快速开发和小型项目,具有更高的灵活性。
TS带来的优势
- 类型安全:通过盾牌图标来表示,意味着 TypeScript 能够在编译时检查类型错误,避免在运行时出现类型相关的问题,提高代码的可靠性。
- 下一代 JS 特性:用 “JS” 图标表示,TypeScript 包含了 ECMAScript 的最新特性,并且可以在不支持这些特性的环境中进行编译和运行。
- 完善的工具链:通过扳手和螺丝刀的图标表示,TypeScript 有强大的开发工具支持,如智能感知、自动补全和重构工具等,能够提高开发效率。
TS不仅仅是一门编程语言,更是一个生产工具可见TS是能够提高开发效率的工具
TS基础
基础类型
- 基本类型
-
boolean, number, string:这些是 TypeScript 中的基本数据类型,分别表示布尔值、数字和字符串。
-
undefined, null:这两个类型表示变量没有值。
undefined通常表示变量已声明但未赋值,null则表示变量被故意设置为空值。 -
any, unknown, void
- any:这个类型可以表示任意类型的值,使用时会失去类型检查的优势。
- unknown:类似于
any,但在使用前必须进行类型检查或断言。 - void:通常用于函数没有返回值的情况。
-
never:这个类型表示永远不会有值的情况,例如函数总是抛出异常或进入无限循环。
-
数组类型
- 数组类型 [] :表示一个可以存储多个值的容器,这些值的类型可以相同也可以不同(在使用
any类型时)。
- 数组类型 [] :表示一个可以存储多个值的容器,这些值的类型可以相同也可以不同(在使用
-
元组类型
- 元组类型 tuple:元组是一种特殊的数组类型,它允许存储不同类型的值,但每个位置的类型是固定的。
右侧的代码示例展示了如何在函数中使用类型断言和错误处理:
- 第一个函数
test接受一个参数x,类型为string | number,并根据x的类型返回不同的值。如果x是string,返回true;如果x是number,返回false;否则抛出错误。 - 第二个函数
throwError接受一个string类型的参数message,并总是抛出一个带有message的Error。
这些内容对于理解 TypeScript 中的类型系统和如何在代码中进行类型检查和处理非常重要。
函数类型
-
函数类型定义
- 在 TypeScript 中定义函数类型时,需要定义输入参数类型和输出类型。
-
输入参数
- 参数支持可选参数和默认参数。
-
输出参数
- 输出可以自动推断,没有返回值时,默认为
void类型。
- 输出可以自动推断,没有返回值时,默认为
-
函数重载
- 名称相同但参数不同的函数,可以通过重载支持多种类型。
interface接口
-
定义:
- 接口是为了定义对象类型。
-
特点:
- 可选属性:使用
?来表示。 - 只读属性:使用
readonly来表示。 - 可以描述函数类型。
- 可以描述自定义属性。
- 可选属性:使用
-
总结:
- 接口非常灵活,具有 “duck typing”(鸭子类型)的特性。
类
-
定义
- 写法和 JS 差不多,增加了一些定义。
-
特点
-
修饰符
- 增加了
public、private、protected修饰符。
- 增加了
-
抽象类
- 只能被继承,不能被实例化。
- 作为基类,抽象方法必须被子类实现。
-
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接受number或string类型的参数,并返回其长度(假设number类型的参数已经被正确处理)。
4. 类型别名(Type Alias vs Interface)
-
定义:类型别名是给类型起一个新名字。
-
相同点:
- 都可以定义对象或函数。
- 都允许继承。
-
差异:
interface是 TS 用来定义对象,type是用来定义别名方便使用。type可以定义基本类型,interface不行。interface可以合并重复声明,type不行。
-
示例代码:
// 类型别名
type Person = {
name: string;
age: number
};
// 接口
interface Person {
name: string;
age: number
};
TS进阶 —— 泛型
基础使用
基本定义:
-
泛型的语法是
<>里面写类型参数,一般用T表示。 -
使用时有两种方法指定类型:
- 定义要使用的类型。
- 通过 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。 -
给出了两种调用方式:
- 显式指定类型为
string:print<string>('hello')。 - 让 TS 自动推断类型:
print('hello')。
- 显式指定类型为
泛型工具类型 —— 基础操作符
-
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及其数组类型的所有键。
-
keyof:获取所有键
- 示例代码:
type Keys = "a" | "b" | "c";
type Obj = {
[p in Keys]: any;
}; // --> { a: any, b: any, c: any }
- 解释:
keyof操作符用于获取类型的所有键。在这个示例中,Keys是一个联合类型,Obj是一个对象类型,其键是Keys中的每个成员。
-
in:遍历枚举类型
- 示例代码:
interface IPerson {
name: string;
age: number;
}
let type1: IPerson['name']; // string
let type2: IPerson['age']; // number
- 解释:
in操作符用于遍历枚举类型。在这个示例中,IPerson是一个接口,type1和type2通过索引访问获取IPerson中的特定类型。
-
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。
-
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 中使用泛型工具类型进行类型操作和约束。
泛型工具类型 —— 常用工具类型
- Partial :这个类型工具可以将类型属性变为可选。示例代码如下:
type Partial<T> = {
[P in keyof T]?: T[P];
};
这里使用了映射类型(Mapped Types),keyof T获取了类型T的所有键,[P in keyof T]?: T[P];表示将T中的每个属性P都变为可选(通过添加?)。
- Required :这个类型工具可以将类型属性变为必选。示例代码如下:
type Required<T> = {
[P in keyof T]-?: T[P];
};
这里使用了-?操作符,表示移除属性的可选性,从而使所有属性变为必选。
- Readonly :这个类型工具可以将类型属性变为只读。示例代码如下:
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
这里使用了readonly关键字,表示将T中的每个属性P都变为只读。
这些工具类型在 TypeScript 中常用于处理对象类型的属性,使得在编写代码时可以更灵活地控制类型的可选性、必选性和只读性。
TS实战
声明文件
- declare:三方库需要类型声明文件。在 TypeScript 中,当使用第三方库时,可能需要声明文件来告诉编译器这些库中的变量、函数和类的类型。
- .d.ts:声明文件定义。这是 TypeScript 中的类型声明文件,通常以.d.ts 为扩展名,用于为 JavaScript 库提供类型信息。
- @types:三方库 TS 类型包。这是一个用于存放 TypeScript 类型定义文件的 npm 包,通常用于为第三方库提供类型定义。
- 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
});
-
代码分析
- 首先定义了一个名为
API的接口,其中包含两个路径/book/detail和/book/comment,并对它们的参数类型进行了约束。 id参数的类型被约束为number,comment参数的类型被约束为string。- 然后定义了一个名为
request的函数,它接受一个 URL 和一个对象作为参数,并使用axios.post发送 POST 请求。 - 在示例调用中,路径被错误地写成了
/book/test,而不是/book/detail或/book/comment。
- 首先定义了一个名为
-
错误原因
- 错误信息是
Argument of type '/book/test' is not assignable to parameter of type 'keyof API'.,这意味着传递给request函数的路径/book/test不在API接口定义的路径中。
- 错误信息是
-
解决方法
- 修正路径,将
/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: '非常棒!'
});
-
代码分析
- 同样使用了
API接口定义的路径和参数类型。 - 在示例调用中,
/book/detail路径下的id参数类型被错误地设置为string,而不是number。
- 同样使用了
-
错误原因
- 错误信息是
Type 'string' is not assignable to type 'number'.,这意味着传递给/book/detail路径的id参数类型不匹配。
- 错误信息是
-
解决方法
- 修正参数类型,将
id参数的类型从string改为number。
- 修正参数类型,将
总结
这两个案例展示了在使用 TypeScript 进行后端接口类型约束时,如何通过接口定义和泛型来确保参数类型的正确性。错误主要是由于路径和参数类型不匹配导致的,解决方法是修正路径和参数类型,使其符合接口定义。