Appearance
泛型对象类型
让我们想象一下,一个可以包含任何数值的盒子类型:字符串、数字、长颈鹿,等等。
typescript
interface Box {
contents: any;
}
现在,内容属性的类型是任意,这很有效,但会导致下一步的意外。
我们可以使用 unknown ,但这意味着在我们已经知道内容类型的情况下,我们需要做预防性检查,或者使用容易出错的类型断言。
typescript
interface Box {
contents: unknown;
}
let x: Box = {
contents: "hello world",
};
// 我们需要检查 'x.contents'
if (typeof x.contents === "string") {
console.log(x.contents.toLowerCase());
}
// 或者用类型断言
console.log((x.contents as string).toLowerCase());
一种安全的方法是为每一种类型的内容搭建不同的盒子类型:
typescript
interface NumberBox {
contents: number;
}
interface StringBox {
contents: string;
}
interface BooleanBox {
contents: boolean;
}
但这意味着我们必须创建不同的函数,或函数的重载,以对这些类型进行操作:
typescript
function setContents(box: StringBox, newContents: string): void;
function setContents(box: NumberBox, newContents: number): void;
function setContents(box: BooleanBox, newContents: boolean): void;
function setContents(box: { contents: any }, newContents: any) {
box.contents = newContents;
}
那是一个很大的模板。此外,我们以后可能需要引入新的类型和重载。这是令人沮丧的,因为我们的盒子类型和重载实际上都是一样的。
相反,我们可以做一个通用的 Box 类型,声明一个类型参数:
typescript
interface Box<Type> {
contents: Type;
}
你可以把这句话理解为:"一个类型的盒子,是它的内容具有类型的东西"。以后,当我们引用 Box 时,我们必须给一个类型参数来代替 Type 。
typescript
let box: Box<string>;
把 Box 想象成一个真实类型的模板,其中 Type 是一个占位符,会被替换成其他类型。当 TypeScript 看到 Box<string> 时,它将用字符串替换 Box<Type> 中的每个 Type 实例,并最终以 { contents: string } 这样的方式工作。换句话说, Box<string> 和我们之前的 StringBox 工作起来是一样的。
typescript
interface Box<Type> {
contents: Type;
}
interface StringBox {
contents: string;
}
let boxA: Box<string> = { contents: "hello" };
boxA.contents;
let boxB: StringBox = { contents: "world" };
boxB.contents;
盒子是可重用的,因为Type可以用任何东西来代替。这意味着当我们需要一个新类型的盒子时,我们根本不需要声明一个新的盒子类型(尽管如果我们想的话,我们当然可以)。
typescript
interface Box<Type> {
contents: Type;
}
interface Apple {
// ....
}
// 等价于 '{ contents: Apple }'.
type AppleBox = Box<Apple>;
这也意味着我们可以完全避免重载,而是使用通用函数。
typescript
function setContents<Type>(box: Box<Type>, newContents: Type) {
box.contents = newContents;
}
值得注意的是,类型别名也可以是通用的。我们可以定义我们新的 Box<Type> 接口:
typescript
interface Box<Type> {
contents: Type;
}
通过使用一个类型别名来代替:
typescript
type Box<Type> = {
contents: Type;
}
由于类型别名与接口不同,它不仅可以描述对象类型,我们还可以用它来编写其他类型的通用辅助类型。
typescript
type OrNull<Type> = Type | null;
type OneOrMany<Type> = Type | Type[];
type OneOrManyOrNull<Type> = OrNull<OneOrMany<Type>>;
type OneOrManyOrNullStrings = OneOrManyOrNull<string>;
我们将在稍后回到类型别名。 通用对象类型通常是某种容器类型,它的工作与它们所包含的元素类型无关。数据结构以这种方式工作是很理想的,这样它们就可以在不同的数据类型中重复使用。