TypeScript 类型补充
TypeScript 提供了一些高级类型,这些类型可以帮助我们:
控制变量的可能值(字面量类型、联合类型、交叉类型等)
增强类型的灵活性和可重用性(泛型、条件类型等)
扩展和修改类型(映射类型、Pick、Omit 等)
这些高级类型使得 TypeScript 在大型应用程序中的类型系统更加强大
基本类型使用
常见的一些类型只需要在后面加一个类型注解就可以
js
let age: number = 18
let myName: string = '老师'
let isLoading: boolean = false
数组类型的两种写法
这里我推荐第一种写法,因为非常清晰
js
// 写法一: let numbers: number[] = [1, 3, 5]
// 写法二: let strings: Array<string> = ['a', 'b', 'c']
联合类型写法
数组中既有 number 类型,又有 string 类型
js
正常我们的数组数据定义是这样子:
let arr= [1,2,3];
加上我们想要约束的数字类型
let arr:number[]= [1,2,3];
如果这个数组里面又有number 类型,又有 string 类型
这个时候我们就需要更改为联合类型:
let arr:(number|string)[]= [1, 'a', 3, 'b'];
类型别名
当上述的代码类型我们多次使用的时候,难免会出现大量的堆积 这个时候我们可以使用类型别名来简化代码,复用
这块说白了,还是抽离和复用思想,简单的很
js
let arrA:(number|string)[]= [1, 'a', 3, 'b'];
let arrB:(number|string)[]= [1, 'a', 3, 'b'];
我们使用类型别名优化以后
type CustomArray = (number | string)[]
let arrA:CustomArray= [1, 'a', 3, 'b'];
let arrB:CustomArray= [1, 'a', 3, 'b'];
TS非模块(Non-modules)
export { } 的作用,是为了解决如下图所示的报错:
JS
Unexpected token 'export'
元组Tuple
项目中可能要使用到地图,涵盖经纬度
js
正常情况下我们的经纬度是这样子的:
let mapsition: number[] = [116.2317, 39.5427]
console.log(mapsition,'mapsition');
但是上面的方式没办法限制我们有几个经纬度,三个肯定错误的
这个时候就用到了元组 Tuple
js
规定两个数字类型的:
let mapsition:[number, number] = [116.2317, 39.5427]
可以看出,元组类型可以确切地标记出有多少个元素,以及每个元素的类型
js
Tuple: 元组类型,表示一个固定长度的数组,其中每个元素的类型可以不同。
let person: [string, number] = ["Alice", 25];
枚举enum
枚举的功能类似于字面量类型+联合类型组合的功能
js
Enum: 枚举类型,表示一组命名的常量。
enum Direction {
Up = 1,
Down,
Left,
Right
}
let dir: Direction = Direction.Up;
js
// 创建枚举
enum Direction { Up, Down, Left, Right }
// 使用枚举类型
function changeDirection(direction: Direction) {
console.log(direction)
}
// 调用函数时,需要应该传入:枚举 Direction 成员的任意一个
// 类似于 JS 中的对象,直接通过 点(.)语法 访问枚举的成员
changeDirection(Direction.Right)
也就是说枚举其实是定义一组命名常量,然后界定在其中一个呗。
如果有个初始值,如何在枚举之中给这些值赋值一个初始值呢
js
// 创建枚举
enum Direction { Up=2, Down=4, Left, Right }
// 使用枚举类型
function changeDirection(direction: Direction) {
console.log(direction)
}
// 调用函数时,需要应该传入:枚举 Direction 成员的任意一个
// 类似于 JS 中的对象,直接通过 点(.)语法 访问枚举的成员
changeDirection(Direction.Right)
数字枚举和字符串枚举有什么不同呢
js
字符串枚举必须是有初始值
// 注意:字符串枚举没有自增长行为,因此,字符串枚举的每个成员必须有初始值
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
}
// 使用枚举类型
function changeDirection(direction: Direction) {
console.log(direction)
}
// 调用函数时,需要应该传入:枚举 Direction 成员的任意一个
// 类似于 JS 中的对象,直接通过 点(.)语法 访问枚举的成员
changeDirection(Direction.Right)
整个演变过程编译 => 被编译为以下 JS 代码:
var Direction;
(function (Direction) {
Direction['Up'] = 'UP'
Direction['Down'] = 'DOWN'
Direction['Left'] = 'LEFT'
Direction['Right'] = 'RIGHT'
})(Direction || Direction = {})
也就是说枚举也是给定的多选一
接口Interfaces
用于定义对象类型的结构,可以包含属性和方法的定义。
JS
interface Person {
name: string;
age: number;
}
let person: Person = { name: "Alice", age: 30 };
👉接口类型
当一个对象类型被多次使用时,一般会使用接口(interface)来描述对象的类型,达到复用的目的
解释
- 使用 interface 关键字来声明接口
- 接口名称(比如,此处的 IPerson),可以是任意合法的变量名称,推荐以 I 开头 声明接口后,直接使用接口名称作为变量的类型
js
所以我们上面的person个人类就可以更改为:
// 接口
interface IPerson {
name: string
age?: number
sayHi:(type:string,total?:string)=>void
}
// 使用类型别名作为对象的类型
let person: IPerson= {
name: 'jack',
age:15,
sayHi(type,total) {
console.log('sayHi');
console.log(type,'type');
console.log(total,'total');
return 'sayHi';
}
}
person.sayHi('type');
console.log(person,'person');
到这里我们发现
**interface(接口)**
和 **type(类型别名)**
竟然几乎是差不多的
interface(接口)和 type(类型别名)的对比:
- 相同点:
- 都可以给对象指定类型
- 不同点:
- interface(接口) 只能为对象指定类型
- 类型别名,不仅可以为对象指定类型,实际上可以为任意类型指定别名
推荐:能使用 type 就是用 type
接口继承
如果两个接口之间有相同的属性或方法,可以将公共的属性或方法抽离出来,通过继承来实现复用
js
还是我们上面的这个接口
// 接口
interface IPerson {
name: string
age?: number
sayHi:(type:string,total?:string)=>void
}
// 使用类型别名作为对象的类型
let person: IPerson= {
name: 'jack',
age:15,
sayHi(type,total) {
console.log('sayHi');
console.log(type,'type');
console.log(total,'total');
return 'sayHi';
}
}
接下来有几个都要用到这种同样的接口
方式一
js
interface IPerson1 {
name: string
age?: number
}
interface IPerson2 {
name: string
age?: number
sayHi:(type:string,total?:string)=>void
}
方式二(接口继承)
extends(继承)
使用 extends(继承)关键字实现了接口 IPerson2 继承 IPerson1
js
// 更好的方式:
interface IPerson1 { name: string;age?: number}
// 继承 IPerson1
interface IPerson2 extends IPerson1 {
sayHi:(type:string,total?:string)=>void
}
// 使用类型别名作为对象的类型
let person: IPerson2= {
name: 'jack',
age:15,
sayHi(type,total) {
console.log('sayHi');
console.log(type,'type');
console.log(total,'total');
return 'sayHi';
}
}
person.sayHi('type');
console.log(person,'person');
字面量类型 (Literal Types)
字面量类型通过字面量值指定一个变量的值只能是某一个特定的值,例如特定的字符串或数字等常量值
👉字面量
js
let color: 'red' | 'blue' | 'black';
let num: 1 | 2 | 3 | 4 | 5;
示例
JS
let status: "success" | "error" = "success";
js
let a: 'apple'; // 只能是 'apple'
a = 'apple'; // 正确
a = 'banana'; // 错误
👉字面量类型
看下面这个字符串的定义,通过上面的类型推论,
js
let str1 = 'Hello TS'
const str2 = 'Hello TS'
//可以从我们的类型推断中看出:
str1 的类型为:string
str2 的类型为:string
//但显然不是,正确的是
str1 的类型为:string
str2 的类型为:‘Hello TS’
这是什么原因呢:
**解释:**
str1 是一个变量(let),它的值可任意变化为其他字符串,所以类型为:string
str2 是一个常量(const),它的值不能变化只能是 ‘Hello TS’,所以,它的类型为:‘Hello TS’
也就是任意 JS 字面量(比如,对象、数字等)都可以作为类型使用
比如鼠标上下左右移动的游戏之中:
js
// 使用自定义类型:
type Direction = 'up' | 'down' | 'left' | 'right'
function changeDirection(direction: Direction) {
console.log(direction)
}
// 调用函数时,会有类型提示:
changeDirection('up')
参数 direction 的值只能是 up/down/left/right 中的任意一个(更精确、严谨)
然后我们下面的结果中可以看到:
在这里我们看到的思想还是一样的:我总结起来就是约束
的思想
可空类型 (Null and Undefined Types)
可以使用 null 和 undefined 作为类型,并允许变量的值为空或未定义。
示例:
js
let value: string | null = null; // 正确
let name: string | undefined = undefined; // 正确
类型保护 (Type Guards)
类型保护是通过某些条件判断来缩小变量类型范围。TypeScript 会根据这些判断推断出变量的更具体类型。
示例:
js
function example(value: string | number) {
if (typeof value === 'string') {
// TypeScript 知道此时 value 是 string 类型
console.log(value.toUpperCase());
} else {
// TypeScript 知道此时 value 是 number 类型
console.log(value.toFixed(2));
}
}
映射类型 (Mapped Types)
映射类型用于根据某个类型生成另一个类型。常用于修改对象属性类型。
示例:
js
type ReadOnly<T> = {
readonly [K in keyof T]: T[K];
};
type Person = { name: string; age: number };
const person: ReadOnly<Person> = { name: 'John', age: 30 };
person.name = 'Alice'; // 错误,name 是只读的
条件类型 (Conditional Types)
条件类型会根据条件选择不同的类型。格式为 T extends U ? X : Y,即如果 T 是 U 的子类型,则为类型 X,否则为类型 Y。
示例:
JS
type IsString<T> = T extends string ? 'yes' : 'no';
type A = IsString<string>; // 'yes'
type B = IsString<number>; // 'no'
索引类型 (Index Types)
索引类型用于根据对象的键获取其值的类型或根据一个类型来构建新类型
索引类型允许我们从对象的属性名动态地推导出属性的类型。
Type写法
JS
type Person = { name: string; age: number };
type NameType = Person['name']; // string
interface写法
JS
interface MyObject {
name: string;
age: number;
}
type NameType = MyObject["name"]; // string
类(Classes)
类本身也可以作为类型,定义类的实例类型。
JS
class Car {
brand: string;
constructor(brand: string) {
this.brand = brand;
}
}
let myCar: Car = new Car("Tesla");
映射类型(Mapped Types)
用于创建对象类型的变体,可以基于现有类型创建新类型。
JS
type ReadOnly<T> = {
readonly [P in keyof T]: T[P];
};
interface Person {
name: string;
age: number;
}
type ReadOnlyPerson = ReadOnly<Person>;
条件类型(Conditional Types)
条件类型允许根据类型进行选择。
JS
type IsString<T> = T extends string ? "Yes" : "No";
type Result = IsString<string>; // "Yes"
type Result2 = IsString<number>; // "No"
类型推断(Type Inference)
TypeScript 在大多数情况下会自动推断变量或函数的类型,这被称为类型推断。你不需要显式指定类型,但仍然能获得类型检查。
JS
let num = 10; // TypeScript 推断 num 为 number 类型
函数类型 (Function Types)
函数类型用于描述函数的参数和返回值类型。可以通过 () 来描述函数的签名。
示例:
JS
type Add = (a: number, b: number) => number;
let add: Add = (x, y) => x + y;
泛型 (Generics)
泛型允许在定义时不指定具体类型,而是在使用时指定类型。这使得类型可以更灵活和可重用。
示例:
JS
function identity<T>(arg: T): T {
return arg;
}
let result = identity(5); // T 推断为 number
let result2 = identity('Hello'); // T 推断为 string
Readonly 类型
Readonly 类型将对象的所有属性变为只读。
示例:
JS
type Person = { name: string; age: number };
const person: Readonly<Person> = { name: 'John', age: 30 };
person.name = 'Alice'; // 错误,name 是只读的
Partial 类型
Partial 类型将对象的所有属性变为可选。
JS
type Person = { name: string; age: number };
const person: Partial<Person> = { name: 'John' }; // age 属性可以不提供
Pick 类型
Pick 类型可以从某个类型中选择一部分属性。
JS
type Person = { name: string; age: number; address: string };
type PersonInfo = Pick<Person, 'name' | 'age'>;
Omit 类型
Omit 类型可以从某个类型中排除指定的属性。
JS
type Person = { name: string; age: number; address: string };
type PersonWithoutAddress = Omit<Person, 'address'>;
👉类型推论
在 TS 中总会有地方不明确指出类型的地方,咋办呢?这个时候,TS 的类型推论机制就会帮助我们提供类型
换句话说:类型注解可以省略不写
- 声明变量并初始化时
- 决定函数返回值时
js
let age = 18
// 变量 age 的类型被自动推断为:number
当函数未定义的时候
function add(num1: number, num2: number) {
return num1 + num2
}
// 上面的add函数返回值的类型被自动推断为:number
类型推断能帮助我们更高的提升技巧
js
能省略类型注解的地方就省略,充分利用TS类型推论的能力
鼠标放在变量名称上,利用 VSCode 的提示来查看类型
在 VSCode 中写代码的时候,多看方法、属性的类型,养成写代码看类型的习惯
👉typeof
TS 提供了 typeof 操作符:
可以在类型上下文中引用变量或属性的类型(类型查询)
简化
js
正常情况下我们下一个计算并返回值:
let p = { x: 1, y: 2 }
function getTotal(point: { x: number; y: number }) {
console.log(point.x + point.y, 'getTotal');
}
getTotal(p)
//使用typeof
let p = { x: 1, y: 2 }
function getTotal(point: typeof p) {
console.log(point.x + point.y, 'getTotal');
}
getTotal(p)
我理解为typeof 其实是类型推断和抽离思想的集合
感觉Ts的使用给我们带来了一种类型的约束,其实更多的是一种辅助开发类型的东西。
类型守卫
any 类型
原则:不推荐使用 any!这会让 TypeScript 变为 “AnyScript”(失去 TS 类型保护的优势)
当值的类型为 any 时,可以对该值进行任意操作,不会有代码提示
js
正常情况下
let obj: number = 0;
obj=100;
当我们写错的时候会给予我们提示:
当时更改为any的时候,所有的提示都已经失效了!
js
let obj: any = 0;
obj='100';
👉类型断言
语法:使用 as 关键字实现类型断言
有时候我们十分明确一个值的类型
js
const aLink = document.getElementById('link')
console.log(aLink, 'aLink');
但是上述获取的只包含所有标签公共的属性或方法,不包含 a 标签特有的 href 等属性
类型断言可以为我们指定更加具体的类型
js
const aLink = document.getElementById('link') as HTMLAnchorElement //类型断言
这里我们可以通过 `__proto__` 属性来获取 DOM 元素的原型对象,然后从原型对象中获取元素的类型。
// 通过 __proto__ 属性获取原型对象
const proto = Object.getPrototypeOf(aLink);
console.log(proto, 'proto');
const elementType = proto.constructor.name;
console.log(elementType, 'elementType-输出元素的类型');