嘿,各位代码界的探险家们,欢迎来到今天的“JS TypeScript
类型系统高级探秘”讲座!今天咱们不搞虚的,直接上干货,一起解锁泛型、条件类型和类型守卫这些TypeScript的强大武器,让你的代码更加健壮、灵活,也让你的秃头进程稍微放缓那么一点点。
第一站:泛型——代码的变形金刚
首先,我们来聊聊泛型。啥是泛型?简单来说,你可以把它想象成一个“类型变量”,就像函数中的参数一样,只不过它代表的是类型。有了它,我们可以编写可以适用于多种类型的代码,避免写一堆重复的、类型不同的函数或类。
举个例子,假设我们需要一个函数,它能返回传入的任何类型的值,并附带一个描述信息。如果没有泛型,你可能需要写很多个函数,每个函数对应一种类型,像这样:
function identityString(arg: string): { value: string, message: string } {
return { value: arg, message: "这是个字符串" };
}
function identityNumber(arg: number): { value: number, message: string } {
return { value: arg, message: "这是个数字" };
}
// ... 更多类型
这样写,不仅代码冗余,而且如果以后需要支持新的类型,还得不断添加新的函数。这时候,泛型就派上用场了!
function identity<T>(arg: T): { value: T, message: string } {
return { value: arg, message: "这是个神奇的值" };
}
let stringResult = identity<string>("Hello");
let numberResult = identity<number>(123);
console.log(stringResult); // { value: "Hello", message: "这是个神奇的值" }
console.log(numberResult); // { value: 123, message: "这是个神奇的值" }
在这个例子中,<T>
就定义了一个类型变量 T
。当你调用 identity<string>("Hello")
时,T
就被推断为 string
类型。当你调用 identity<number>(123)
时,T
就被推断为 number
类型。是不是很酷?
泛型接口与类
泛型不仅可以用于函数,还可以用于接口和类。
interface GenericIdentityFn<T> {
(arg: T): { value: T, message: string };
}
let myIdentity: GenericIdentityFn<number> = identity; // 假设 identity 函数已经定义
console.log(myIdentity(42)); // { value: 42, message: "这是个神奇的值" }
上面的代码定义了一个泛型接口 GenericIdentityFn
,它描述了一个接受类型为 T
的参数,并返回一个包含类型为 T
的 value
和 message
属性的对象的函数。
再来看看泛型类:
class DataHolder<T> {
data: T;
constructor(data: T) {
this.data = data;
}
getData(): T {
return this.data;
}
}
let stringHolder = new DataHolder<string>("TypeScript");
let numberHolder = new DataHolder<number>(3.14);
console.log(stringHolder.getData()); // TypeScript
console.log(numberHolder.getData()); // 3.14
DataHolder
类可以存储任何类型的数据,并且通过 getData
方法获取。
泛型约束
有时候,我们希望泛型类型具有某些特定的属性或方法。这时,我们可以使用泛型约束。
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
loggingIdentity("Hello"); // OK
loggingIdentity([1, 2, 3]); // OK
// loggingIdentity(123); // Error: Argument of type 'number' is not assignable to parameter of type 'Lengthwise'.
在这个例子中,T extends Lengthwise
表示 T
必须满足 Lengthwise
接口的约束,也就是说,它必须有一个 length
属性。
总结一下泛型的好处:
优点 | 描述 |
---|---|
代码复用性 | 编写一次,可以用于多种类型。 |
类型安全 | 在编译时进行类型检查,避免运行时错误。 |
更好的可读性 | 通过类型参数,可以更清晰地表达代码的意图。 |
第二站:条件类型——类型世界的 if-else
接下来,我们进入条件类型的世界。条件类型允许我们根据某个条件来选择不同的类型,就像代码中的 if-else
语句一样。
它的语法是这样的:T extends U ? X : Y
。意思是如果类型 T
可以赋值给类型 U
,那么结果类型就是 X
,否则就是 Y
。
一个简单的例子:
type IsString<T> = T extends string ? true : false;
type StringResult = IsString<string>; // true
type NumberResult = IsString<number>; // false
IsString
类型接收一个类型参数 T
,如果 T
是 string
类型,那么结果就是 true
,否则就是 false
。
infer
关键字
条件类型常常和 infer
关键字一起使用。infer
关键字允许我们在条件类型中推断类型变量。
例如,我们可以使用条件类型和 infer
来提取函数返回值的类型:
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
function add(a: number, b: number): number {
return a + b;
}
type AddReturnType = ReturnType<typeof add>; // number
在这个例子中,T extends (...args: any) => any
约束 T
必须是一个函数类型。然后,T extends (...args: any) => infer R ? R : any
使用 infer R
推断函数返回值的类型,并将其赋值给 R
。如果 T
不是函数类型,那么结果就是 any
。
Exclude
和 Extract
类型
TypeScript 内置了一些非常有用的条件类型,例如 Exclude
和 Extract
。
Exclude<T, U>
用于从类型 T
中排除可以赋值给类型 U
的类型。
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
Extract<T, U>
用于从类型 T
中提取可以赋值给类型 U
的类型。
type T3 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T4 = Extract<string | number | (() => void), Function>; // () => void
应用场景:类型映射
条件类型还可以用于类型映射,例如,我们可以将一个对象的所有属性变为只读:
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface Person {
name: string;
age: number;
}
type ReadonlyPerson = Readonly<Person>;
// ReadonlyPerson 类型等价于:
// interface ReadonlyPerson {
// readonly name: string;
// readonly age: number;
// }
条件类型总结:
特性 | 描述 |
---|---|
T extends U ? X : Y |
根据 T 是否可以赋值给 U 来选择类型 X 或 Y 。 |
infer |
用于在条件类型中推断类型变量。 |
Exclude<T, U> |
从类型 T 中排除可以赋值给类型 U 的类型。 |
Extract<T, U> |
从类型 T 中提取可以赋值给类型 U 的类型。 |
第三站:类型守卫——类型世界的侦探
最后,我们来学习类型守卫。类型守卫是一种在运行时缩小变量类型范围的技术。它可以帮助 TypeScript 更好地理解你的代码,并提供更准确的类型检查。
TypeScript 提供了几种类型守卫的方式:
typeof
类型守卫
typeof
类型守卫用于判断变量的类型。
function printValue(value: string | number) {
if (typeof value === "string") {
console.log(value.toUpperCase()); // TypeScript 知道 value 是 string 类型
} else {
console.log(value.toFixed(2)); // TypeScript 知道 value 是 number 类型
}
}
printValue("hello");
printValue(123.456);
instanceof
类型守卫
instanceof
类型守卫用于判断一个对象是否是某个类的实例。
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
}
class Dog extends Animal {
bark() {
console.log("Woof!");
}
}
function makeSound(animal: Animal) {
if (animal instanceof Dog) {
animal.bark(); // TypeScript 知道 animal 是 Dog 类型
} else {
console.log("Generic animal sound");
}
}
let dog = new Dog("Buddy");
let animal = new Animal("Generic animal");
makeSound(dog); // Woof!
makeSound(animal); // Generic animal sound
- 自定义类型守卫
我们可以使用 is
关键字来定义自定义类型守卫。
interface Bird {
fly(): void;
layEggs(): void;
}
interface Fish {
swim(): void;
layEggs(): void;
}
function isBird(animal: Bird | Fish): animal is Bird {
return (animal as Bird).fly !== undefined;
}
function doSomething(animal: Bird | Fish) {
if (isBird(animal)) {
animal.fly(); // TypeScript 知道 animal 是 Bird 类型
} else {
animal.swim(); // TypeScript 知道 animal 是 Fish 类型
}
}
let myBird: Bird = {
fly: () => console.log("Flying"),
layEggs: () => console.log("Laying eggs")
};
let myFish: Fish = {
swim: () => console.log("Swimming"),
layEggs: () => console.log("Laying eggs")
};
doSomething(myBird); // Flying
doSomething(myFish); // Swimming
在这个例子中,isBird(animal: Bird | Fish): animal is Bird
定义了一个类型守卫函数。如果 isBird(animal)
返回 true
,那么 TypeScript 就会认为 animal
是 Bird
类型。
类型守卫总结:
类型守卫方式 | 描述 |
---|---|
typeof |
判断变量的类型。 |
instanceof |
判断一个对象是否是某个类的实例。 |
自定义类型守卫 | 使用 is 关键字定义自定义类型守卫函数。 |
总结
好了,各位代码界的探险家们,今天的“JS TypeScript
类型系统高级探秘”讲座就到这里。我们一起探索了泛型、条件类型和类型守卫这三大神器。希望这些知识能帮助你写出更健壮、更灵活、更易于维护的 TypeScript 代码。
记住,熟能生巧,多练习,多实践,你也能成为类型系统的大师!下次再见!