1. 认识TS
什么是TS?
TS(TypeScript)是一种由Microsoft开发和维护的编程语言,它是JavaScript的超集,支持静态类型检查、类、接口、泛型等特性。TS最终会被编译为标准的JavaScript代码,因此可以运行在任何支持JavaScript的环境中。
使用TS带来了许多好处,例如更早地发现类型相关的错误、提高代码可读性、使用面向对象的编程范式等。同时,由于TS与JS具有相似的语法和语义,因此学习曲线相对较低,也很容易与现有的JavaScript项目进行集成。
TS 相关学习资源
2. TS 基础
TS里面的基础数据类型
在TypeScript中,基本数据类型包括:
number
:表示数字类型,包括整数和浮点数。例如:
ts
let age: number = 18; let price: number = 9.99;
string
:表示字符串类型。例如:
ts
let name: string = "Alice"; let message: string = `Hello, ${name}!`;
boolean
:表示布尔类型,只有true
和false
两个值。例如:
ts
let isStudent: boolean = true; let hasJob: boolean = false;
null
和undefined
:表示空值和未定义的值。例如:
ts
let emptyValue: null = null; let notDefined: undefined = undefined;
symbol
:表示独一无二的值。例如:
ts
let uniqueKey: symbol = Symbol('key'); let anotherKey: symbol = Symbol('key'); console.log(uniqueKey === anotherKey); // false
any
AnyScript🫢😏
除此之外,还有一种特殊的类型叫做 any
,它可以存储任意值,不会进行类型检查。例如:
ts
let anything: any = 'hello'; anything = 42; anything = true;
以上是常用的基础数据类型,需要注意的是,在 TypeScript 中,变量声明时可以省略数据类型,TypeScript 会根据变量的初始值
自动推断
出数据类型。例如:
ts
let count = 10; // 推断为 number 类型 let message = "Hello!"; // 推断为 string 类型 let flag = true; // 推断为 boolean 类型
TS 里面的高级数据类型
除了上述基本数据类型外,TypeScript 还提供了以下几种高级数据类型:
1.Array
:表示数组
类型,可以存储一组相同类型的值。例如:
ts
let numbers: number[] = [1, 2, 3]; let names: string[] = ["Alice", "Bob", "Charlie"];
也可以使用泛型来声明数组类型:
ts
let scores: Array<number> = [80, 90, 95];
2.Tuple
:表示元组
类型,用于存储固定长度和类型的数组。例如:
ts
let user: [string, number] = ["Alice", 18];
3.Enum
:表示枚举
类型,用于定义一组常量。例如:
ts
enum Color { Red, Green, Blue } let color: Color = Color.Red;
4.Object
:表示对象类型
,用于存储键值对。例如:
ts
let person: {name: string, age: number} = { name: "Alice", age: 18 };
5.Function
:表示函数类型
,包括参数类型和返回值类型。例如:
ts
function add(x: number, y: number): number { return x + y; }
6.Void
:表示空类型
,通常用于没有返回值
的函数
。例如:
ts
function sayHello(): void { console.log("Hello!"); }
7.Never
:表示永远不存在
的值的类型,通常用于抛出异常
或者无限循环等操作
。例如:
ts
function throwError(message: string): never { throw new Error(message); } function infiniteLoop(): never { while (true) {} }
详解TS 里面的函数类型
定义: TS定义函数类型时要定义输入参数类型和 输出类型
输入参数: 参数支持可选参数和默认参数
输出参数: 输出可以自动推断,没有返回值时,默认为void 类型
函数重载: 名称相同 但是参数不同, 可以通过重载支持多种类型
ts
// 1.定义 参数以及类型 返回值以及类型 ❓ 表示可传可不传 let L = function (name: string, age: number, hoby?: string): string { return name + age } let L1 = L("张三", 20) // "张三20"
TS interface 接口
在 TypeScript 中,interface
是一种用于描述对象类型的语法,它定义了一个对象
应该具有哪些属性和方法
。接口可以被其他类型或者函数实现(implement)
或扩展(extend)
,让代码更加灵活和可读性强。
可选
属性 :?
表示该属性在写入的时候可填可不填只读
属性 :readonly
关键字表示该属性只可以读取,但不可以修改- 可以
描述函数类型
- 可以
描述自定义属性
总结: 接口非常灵活 duck typing
以下是一个简单的 interface
示例:
ts
interface Person { name: string; // name的属性值必须是string类型 age: number; // age的属性值必须是number类型 } let person: Person = { name: "Alice", age: 18 };
上面的代码定义了一个名为 Person
的接口,它有两个属性: name
和 age
,分别表示人的姓名和年龄。使用 Person
接口来声明一个变量时,必须遵循 Person
接口的属性定义。
可以使用 extends
关键字来扩展接口,例如:
ts
interface Employee extends Person { jobTitle: string; } let employee: Employee = { name: "Bob", age: 25, jobTitle: "Developer" };
上面的代码定义了一个名为 Employee
的接口,它继承了 Person
接口,并新增了一个 jobTitle
属性。
另外,接口不仅可以用来描述对象类型,还可以用来描述函数类型。例如:
ts
interface Add { (x: number, y: number): number; } let add: Add = function(x, y) { return x + y; };
上面的代码定义了一个名为 Add
的接口,它描述了一个接受两个参数并返回一个数字类型的函数。使用 Add
接口来声明一个变量 add
,并将一个函数赋值给它,该函数满足 Add
接口的定义。
综上所述,interface
是 TypeScript 中描述复杂对象和函数类型的重要语法之一,它可以提高代码的可读性和灵活性。
3. TS 进阶
类型操作符
在 TypeScript 中,有三种常见的类型操作符:交叉类型
、联合类型
和类型断言。
1. 交叉类型 &
交叉类型(Intersection Types)
是将多个类型合并成一个类型,表示这个类型包含了多个类型的所有特性
。使用 &
符号连接多个类型即可。例如:
ts
interface Person { name: string; } interface Employee { jobTitle: string; } type PersonWithJob = Person & Employee; let personWithJob: PersonWithJob = { name: "Alice", jobTitle: "Developer" };
上面的代码中,定义了两个接口 Person
和 Employee
,然后使用交叉类型操作符 &
将它们合并为一个新的类型 PersonWithJob
,表示既是 Person
又是 Employee
的对象。最后用 personWithJob
表示一个具有 PersonWithJob
类型的对象。
ts
let num: number | string // num这个变量可以是number 也可以是 string 类型 num = 10; //ok num = "10" // ok
2. 联合类型 |
2.联合类型(Union Types)
是指可以表示多个类型中的一种的类型
。使用 |
符号连接多个类型即可。例如:
ini
type NumberOrString = number | string; let value1: NumberOrString = 123; // ok let value2: NumberOrString = "abc"; // ok let value3: NumberOrString = true; // error
上面的代码中,定义了一个联合类型 NumberOrString
,表示可以是数字
或字符串类型
。然后用 value1
和 value2
分别赋值为数字和字符串,都是合法的。但是尝试将 value3
赋值为布尔值时,会得到一个类型错误。
3. 类型断言 as <>
3.类型断言(Type Assertion)
是指在编译器无法确定类型时
,手动告诉编译器它的类型
。可以使用尖括号 <>
语法或 as
关键字来进行类型断言。例如:
ini
let value: any = "123"; let length1: number = (<string>value).length; let length2: number = (value as string).length;
上面的代码中,定义了一个变量 value
,它的类型为 any
,即未知类型
。然后使用类型断言获取 value
的长度,并将结果赋值给变量 length1
和 length2
。两种方式都可以实现类型转换,但是推荐使用 as
关键字的语法。
4. 类型别名
定义: 给类型起一个别名
相同点:
- 都可以定义对象或者函数
- 都允许继承
差异点:
- interface 是Ts用来定义对象, type 是用来定义别名方便使用
- type 可以定义基本数据类型,interface 不行;
- interface 可以合并重复声明,type不行
ts
// 声明一个对象的类型时,有两个主要的工具:接口(interface)和类型别名(type aliases)。 // // 他们非常相似,并且在大多数情况下是相同的。 type BirdType = { wings: 2; }; interface BirdInterface { wings: 2; } const bird1: BirdType = { wings: 2 }; const bird2: BirdInterface = { wings: 2 }; // 因为 TypeScript 有着结构化类型系统。 // 我们也可以混合使用他们。 const bird3: BirdInterface = bird1; // 他们都支持扩展另一个些接口或类型。 // 类型别名通过并集类型来实现,接口通过 extends 关键字。 type Owl = { nocturnal: true } & BirdType; type Robin = { nocturnal: false } & BirdInterface; interface Peacock extends BirdType { colourful: true; flies: false; } interface Chicken extends BirdInterface { colourful: false; flies: false; } let owl: Owl = { wings: 2, nocturnal: true }; let chicken: Chicken = { wings: 2, colourful: false, flies: false }; // 也就是说,我们建议您使用接口而不是类型别名,因为你可以在接口中获得更好的错误提示。 // 如果你将鼠标悬停在下面的错误上,你会看到在使用接口(例如 Chicken)时, // TypeScript 会提供更简洁的提示信息。 owl = chicken; chicken = owl; // 一个接口和类型别名的主要区别是,接口是开放的,类型别名是封闭的。 // 这意味着你可以你可以通过多次声明同一个接口来扩展它。 interface Kitten { purrs: boolean; } interface Kitten { colour: string; } // 与此同时,类型别名不可以在外部变更它的声明。 type Puppy = { color: string; }; type Puppy = { toys: number; }; // 基于你不同的目的,这个区别可以是证明的也可以是负面的。 // 一般来说,对于公开的需要暴露的类型,将他们作为接口是更好的选择。 // 要查看接口和类型定义之间所有边际条件,下面的 StackOverflow 讨论是最好的资源之一: // https://stackoverflow.com/questions/37233735/typescript-interfaces-vs-types/52682220#52682220
4. 泛型
1. 什么时候需要泛型
2. 泛型是什么
泛型(Generics)是一种参数化类型
的机制,可以让我们在定义函数、类和接口
时,使用一个或多个类型
作为参数
来指定其返回值
或成员的类型
。这样可以提高代码的复用性和可读性,同时也能保证类型安全。
就是临时占个位置, 之后通过传递过来的参数进行推导
使用泛型时,我们需要在函数、类或接口名称后面加上尖括号 <T>
,其中 T 代表泛型类型参数名。然后就可以在函数内部或类/接口成员的声明中使用这个泛型类型了。
3. 如何使用
下面是一个简单的泛型函数示例,它将传入的数组反转并返回:
ts
function reverse<T>(array: T[]): T[] { return array.reverse(); } let numbers = [1, 2, 3]; let reversedNumbers = reverse(numbers); // inferred type: number[] console.log(reversedNumbers); // Output: [3, 2, 1] let words = ["apple", "banana", "cherry"]; let reversedWords = reverse(words); // inferred type: string[] console.log(reversedWords); // Output: ["cherry", "banana", "apple"]
在上面的代码中,我们定义了一个泛型函数 reverse
,它接受一个类型为 T
的数组参数,并返回一个类型为 T
的数组。使用推断类型的方式调用这个函数时,TypeScript 会自动根据传入参数的类型推导出泛型类型 T
的具体类型。
需要注意的是,在使用泛型时,我们还可以对泛型类型参数进行约束,以限制它们只能是某个特定类型或其子类型。例如:
ts
interface Lengthwise { length: number; } function logLength<T extends Lengthwise>(arg: T): void { console.log(arg.length); } let str = "hello"; logLength(str); // Output: 5 let obj = { length: 10 }; logLength(obj); // Output: 10 let num = 123; logLength(num); // Error: 'number' has no 'length' property
在上面的代码中,我们定义了一个接口 Lengthwise
,它包含一个名为 length
的数值属性。然后定义了一个泛型函数 logLength
,它接受一个泛型类型参数 T
,但是要求 T
必须满足 extends Lengthwise
,也就是说必须具有 length
属性。
最后使用 str
、obj
和 num
三个变量分别调用 logLength
函数,可以看到只有 str
和 obj
能够成功输出结果,而 num
因为没有 length
属性而导致编译错误。
4. 泛型工具类型-基础操作符
5. TS 实战
声明文件
- declare : 第三方库需要类型声明文件
- .d.ts : 声明文件定义
- @type: 第三方库TS类型包
- tsconfig.json: 定义TS的配置
配置分类(compilerOptions 选项)
typescript
{ "compilerOptions": { /* 基本选项 */ "target": "es5", // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT' "module": "commonjs", // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015' "lib": [], // 指定要包含在编译中的库文件 "allowJs": true, // 允许编译 javascript 文件 "checkJs": true, // 报告 javascript 文件中的错误 "jsx": "preserve", // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react' "declaration": true, // 生成相应的 '.d.ts' 文件 "sourceMap": true, // 生成相应的 '.map' 文件 "outFile": "./", // 将输出文件合并为一个文件 "outDir": "./", // 指定输出目录 "rootDir": "./", // 用来控制输出目录结构 --outDir. "removeComments": true, // 删除编译后的所有的注释 "noEmit": true, // 不生成输出文件 "importHelpers": true, // 从 tslib 导入辅助工具函数 "isolatedModules": true, // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似). /* 严格的类型检查选项 */ "strict": true, // 启用所有严格类型检查选项 "noImplicitAny": true, // 在表达式和声明上有隐含的 any类型时报错 "strictNullChecks": true, // 启用严格的 null 检查 "noImplicitThis": true, // 当 this 表达式值为 any 类型的时候,生成一个错误 "alwaysStrict": true, // 以严格模式检查每个模块,并在每个文件里加入 'use strict' /* 额外的检查 */ "noUnusedLocals": true, // 有未使用的变量时,抛出错误 "noUnusedParameters": true, // 有未使用的参数时,抛出错误 "noImplicitReturns": true, // 并不是所有函数里的代码都有返回值时,抛出错误 "noFallthroughCasesInSwitch": true, // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿) /* 模块解析选项 */ "moduleResolution": "node", // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6) "baseUrl": "./", // 用于解析非相对模块名称的基目录 "paths": {}, // 模块名到基于 baseUrl 的路径映射的列表 "rootDirs": [], // 根文件夹列表,其组合内容表示项目运行时的结构内容 "typeRoots": [], // 包含类型声明的文件列表 "types": [], // 需要包含的类型声明文件名列表 "allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入。 /* Source Map Options */ "sourceRoot": "./", // 指定调试器应该找到 TypeScript 文件而不是源文件的位置 "mapRoot": "./", // 指定调试器应该找到映射文件而不是生成文件的位置 "inlineSourceMap": true, // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件 "inlineSources": true, // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性 /* 其他选项 */ "experimentalDecorators": true, // 启用装饰器 "emitDecoratorMetadata": true // 为装饰器提供元数据的支持 } }
6. 项目总结