深入浅出TypeScript
为什么
🥰 ts 的使用度逐年上升,ts 社区的活跃度也在明显上升。
TS 是 JS 的超集。
TS 相对于 JS 有哪些优势 🧐:
- 用于解决大型项目的代码复杂性
- 强类型,支持静态和动态类型(动态弱类型语言)
- 可以在编译期间发现并纠正错误(JS 只能在运行时发现错误)
- 不允许改变变量的数据类型(JS 变量可以被赋予成不同类型)
TS 不仅是一门语言,更是生产力工具。它提供了相对完善的类型检查,同时也带来了一套完整的工具链,可以结合 IDE 的使用来提高代码的质量。
推荐教程:
- Awesome TypeScript:TS 开源教程及应用
- TypeScript Playground:TS 到 JS 在线编译
TS 基础
基础类型
-
boolean, number, string
-
✨ 枚举 enum
使用
enum可以让我们更清晰地表达代码的意图,提高代码的可读性。enum Response { No = 0, Yes = 1, } function respond(recipient: string, message: Response) { // ... } respond('Alice', Response.Yes); -
✨ any, unkown, void
-
✨ never
我们需要 never 类型,因为它可以帮助我们更好地描述代码的行为。例如,在上面的例子中,使用 never 类型可以清楚地表明 raiseError 函数永远不会返回。此外,由于 never 类型是所有类型的子类型,它还可以用于类型检查和类型推断。例如,在使用类型保护缩小变量类型时,如果某个分支永远不会发生,那么该分支的类型就是 never 类型。这样可以帮助我们更好地理解代码,并避免潜在的错误。
function raiseError(message: string): never { throw new Error(message); } -
array
-
tuple
函数类型
TS 定义函数类型时要定义输入参数类型和输出类型。
-
输入参数:支持可选参数和默认参数
-
输出参数:可以自动推断,没有返回值时默认为 void
-
函数重载:名称相同参数不同,可以通过重载支持多种类型
在 TypeScript 中,🪧 函数重载是指在同一个作用域内定义多个同名函数,但它们的参数类型和数量不同。当调用这个函数时,TypeScript 会根据传入的参数类型和数量来选择正确的函数实现。通过使用函数重载,我们可以更好地描述函数的行为,并提供更好的类型检查和代码提示。
// 函数重载使用案例 interface User { id: number; name: string; } function getUser(id: number): User; function getUser(name: string): User; function getUser(param: number | string): User { if (typeof param === 'number') { // 根据用户 ID 获取用户信息 return { id: param, name: 'Alice' }; } else { // 根据用户名获取用户信息 return { id: 1, name: param }; } } const user1 = getUser(1); // 根据用户 ID 获取用户信息 const user2 = getUser('Bob'); // 根据用户名获取用户信息
interface
🎯 接口是为了定义对象类型。
-
可选属性 ?
interface User { id: number; name: string; age?: number; // 可选属性 } -
只读属性 readonly
interface User { readonly id: number; // 只读属性 name: string; } -
可以描述函数类型
// 一个描述函数类型的接口,接受两个 string 类型的参数,并返回一个 boolean 类型的值 interface SearchFunc { (source: string, subString: string): boolean; } let mySearch: SearchFunc; mySearch = function(source: string, subString: string) { let result = source.search(subString); return result > -1; } -
可以描述自定义属性
interface StringArray { [index: number]: string; }
类
🎯 写法基本与 JS 一致,增加了一些定义。
特点:
-
增加了 public. private, protected 修饰符
-
抽象类:
- 只能被继承,不能被实例化
- 作为基类,抽象方法必须被子类所实现
-
interface 约束类,使用 implements 关键字
TypeScript 高级进阶
-
联合类型 |
-
交叉类型 &
interface A { a: string; } interface B { b: number; } let x: A & B; x = { a: 'hello', b: 42 }; // OK x = { a: 'hello' }; // Error: Property 'b' is missing in type '{ a: string; }' but required in type 'B' -
类型断言
let x: any = 'hello'; let y = (<string>x).length; // OK let z = (x as string).length; // OK -
类型别名(type or interface)
- 给类型起别名
- 涉及类时使用 interface,不涉及类时一般都是 type
type MyString = string; let x: MyString = 'hello'; // OK
泛型
⌛️ 组件要支持当前的数据类型,也要能够支持未来的数据类型。
基本定义 💡
-
泛型的语法是 <> 里写类型参数,一般用 T 来表示
-
使用时有两种方法指定类型
- 定义要使用的类型
- 通过 TS 类型推断,自动推导类型
-
泛型的作用是临时占位,之后通过传来的类型进行推导
function print<T>(arg:T):T {
console.log(arg);
return arg;
}
// 指定类型
print<string>('hello!');
// 自动推导类型
print('hello!');
基础操作符 🔨
-
typeof:获取类型
// 设置 Sam 的类型与 sam 的类型相同 type Sam = typeof sam; -
keyof: 获取所有键
// 获取 Person 接口中所有数据类型 type H1 = keyof Person; // 获取 Person 接口中所有数据 type H2 = keyof Person[]; -
in:便利枚举
type Keys "a" | "b" | "c" type Obj = { [p in Keys]: any } // {a: any, b: any, c: any} -
T[K]:索引访问
interface IPerson { name: string; } let type1: IPerson['name'] //string -
extends:泛型约束
我们定义了一个名为 HasLength 的接口,它包含一个 length 属性。然后,我们定义了一个名为 getLength 的泛型函数,并使用泛型约束来限制类型参数 T 必须扩展自 HasLength 接口。这样,在调用该函数时,我们只能传递包含 lengthd 属性的对象。
interface HasLength { length: number; } function getLength<T extends HasLength>(obj: T): number { return obj.length; } // 使用泛型约束 const len1 = getLength([1, 2, 3]); // 类型为 number const len2 = getLength('abc'); // 类型为 number const len3 = getLength({ length: 10 }); // 类型为 number
常用工具类型
- Partial:将类型属性变为可选
- Required:将类型属性变为必选
- ReadOnly:将类型属性变为只读
- Pick, Record ...
TypeScript 实战
声明文件
-
declare:三方库需要类型声明文件
declare var $: any; -
.d.ts:声明文件定义
// jquery.d.ts declare var $: any;
- @types:三方库 TS 类型包
- tsconfig.json:定义 TS 的配置
案例 - ⚗️泛型约束后端接口类型
使用泛型约束来定义后端接口类型的好处 💪 在于,它可以帮助您在编写代码时捕获类型错误。
例如,在下面的例子中,如果尝试访问响应数据中不存在的属性,TypeScript 将会在编译时报错。
// 定义 User 接口,表示用户信息
interface User {
id: number;
name: string;
email: string;
}
// 定义 ApiResponse 泛型接口,表示后端接口的响应数据
interface ApiResponse<T> {
data: T;
error?: string;
}
type GetUserResponse = ApiResponse<User>;
async function getUser(id: number): Promise<GetUserResponse> {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
return data;
}
我们定义了一个类型别名 GetUserResponse,它表示获取用户信息接口的响应类型。该类型别名使用了泛型约束,将 ApiResponse 接口的类型参数 T 指定为 User 类型。
之后,我们定义了一个名为 getUser 的异步函数,用于从后端获取用户信息。该函数的返回类型为 Promise,表示它返回一个解析为 GetUserResponse 类型的 Promise 对象。
补充
避免沦为 AnyScript
- 重构时,为了减小工作成本可以适当使用一些 any
- 新项目中,尽量提前做好类型定义,避免过多使用 any