TypeScript:从类型创建类型

你好,未来的TypeScript大师们!我很高兴能成为你们在这个激动人心的TypeScript类型世界中的向导。作为一名有着多年经验的计算机科学教师,我亲眼见证了理解类型是如何将初学者转变为编码高手的。那么,让我们一起开始这段冒险吧!

TypeScript - Creating Types from Types

联合类型

想象你在一个冰淇淋店,你可以选择巧克力或者香草。在TypeScript中,我们称这种选择为“联合类型”。就像是说,“我要这个或者那个。”

让我们深入一些代码:

type Flavor = "chocolate" | "vanilla";

let myIceCream: Flavor;
myIceCream = "chocolate"; // 这是对的
myIceCream = "strawberry"; // 错误!类型 '"strawberry"' 不能赋值给类型 'Flavor'。

在这个例子中,Flavor 是一个联合类型,可以是 "chocolate" 或 "vanilla"。当我们尝试将 "strawberry" 赋值给 myIceCream 时,TypeScript会说,“等等!那不在菜单上!”

联合类型在你想要限制变量可能有的值时非常有用。这里再举一个例子:

type Answer = "yes" | "no" | "maybe";

function respondToQuestion(answer: Answer) {
if (answer === "yes") {
console.log("太好了!");
} else if (answer === "no") {
console.log("哦不!");
} else {
console.log("我看出你还没决定。");
}
}

respondToQuestion("yes"); // 没问题
respondToQuestion("perhaps"); // 错误!参数类型 '"perhaps"' 不能赋值给参数类型 'Answer'。

交叉类型

现在,让我们来谈谈交叉类型。如果联合类型是关于“这个或那个”,交叉类型就是关于“这个和那个”。就像在快餐店点套餐——你得到汉堡、薯条和饮料。

下面是如何在代码中表示:

type Name = {
firstName: string;
lastName: string;
};

type Age = {
age: number;
};

type Person = Name & Age;

let john: Person = {
firstName: "John",
lastName: "Doe",
age: 30
};

在这个例子中,Person 是一个交叉类型,它结合了 NameAge。就像说,“一个人必须有名字、姓氏和年龄。”

这里还有一个有趣的例子:

type Swimmer = {
swim: () => void;
};

type Flyer = {
fly: () => void;
};

type FlyingFish = Swimmer & Flyer;

let nemo: FlyingFish = {
swim: () => console.log("在大海中游泳"),
fly: () => console.log("在波浪上飞翔")
};

nemo.swim(); // 输出:在大海中游泳
nemo.fly();  // 输出:在波浪上飞翔

实用类型

TypeScript 提供了一套实用类型,帮助我们操作和转换现有的类型。它们就像是类型世界的瑞士军刀!让我们来看看一些最常用的实用类型:

Partial

Partial<T> 使得类型 T 的所有属性变为可选。就像是说,“我想要所有这些,但我不需要它们现在都具备。”

interface Todo {
title: string;
description: string;
completed: boolean;
}

function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate };
}

const myTodo = {
title: "学习 TypeScript",
description: "学习2小时",
completed: false
};

const updatedTodo = updateTodo(myTodo, {
description: "学习3小时"
});

console.log(updatedTodo);
// 输出:{ title: "学习 TypeScript", description: "学习3小时", completed: false }

Pick<T, K>

Pick<T, K> 通过从 T 中选择一组属性 K 来构造一个类型。就像是挑水果篮中最喜欢的水果。

interface User {
id: number;
name: string;
email: string;
age: number;
}

type UserSummary = Pick<User, "id" | "name">;

const userSummary: UserSummary = {
id: 1,
name: "John Doe"
};

Omit<T, K>

Omit<T, K> 通过从 T 中选择所有属性然后移除 K 来构造一个类型。它是 Pick 的反面。

interface Product {
id: number;
name: string;
price: number;
description: string;
}

type ProductPreview = Omit<Product, "description">;

const productPreview: ProductPreview = {
id: 1,
name: "酷炫小工具",
price: 99.99
};

下面是一个总结这些实用类型的表格:

实用类型 描述
Partial 使得类型 T 的所有属性变为可选
Pick<T, K> 通过从 T 中选择一组属性 K 来构造一个类型
Omit<T, K> 通过从 T 中选择所有属性然后移除 K 来构造一个类型

typeof 类型操作符

TypeScript 中的 typeof 类型操作符允许我们根据现有值的形状创建一个类型。就像是根据一个对象制作一个模具来创建更多形状相同的对象。

让我们看一个例子:

const user = {
name: "John",
age: 30,
location: "New York"
};

type User = typeof user;

const anotherUser: User = {
name: "Jane",
age: 25,
location: "London"
};

在这个例子中,我们使用 typeof user 来创建一个新的类型 User,它与我们的 user 对象的形状相匹配。这在你想确保多个对象具有相同的结构时非常有用。

这里还有一个 typeof 派上用场的例子:

const colors = ["red", "green", "blue"] as const;
type Color = typeof colors[number];

let myColor: Color = "red"; // 可以
myColor = "yellow"; // 错误:类型 '"yellow"' 不能赋值给类型 '"red" | "green" | "blue"'。

在这个情况下,我们使用 typeof 和索引访问类型来从常量数组创建一个联合类型。

那么,各位,以上就是我们的TypeScript类型之旅,从现有的类型创建新的类型。记住,熟能生巧,所以不要害怕在你的项目中尝试这些概念。快乐编码,愿类型永远站在你这边!

Credits: Image by storyset