共计 15912 个字符,预计需要花费 40 分钟才能阅读完成。
TypeScript 的学习材料十分多,其中也不乏很多优良的文章和教程。然而目前为止没有一个我特地称心的。起因有:
- 它们大多数没有一个清晰的主线,而是依照 API 组织章节的,内容在 逻辑上 比拟零散。
- 大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。
- 大多数内容比拟干燥,趣味性比拟低。都是水灵灵的文字,没有图片,不足可能引起强烈共鸣的例子。
因而我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮忙大家建设 TypeScript 世界观。
系列安顿:
- 上帝视角看 TypeScript
- TypeScript 类型零碎
- types 和 @types 是什么?
- 你不晓得的 TypeScript 泛型(万字长文,倡议珍藏)
- TypeScript 配置文件该怎么写?
- TypeScript 是如何与 React,Vue,Webpack 集成的?
- TypeScript 练习题(就是本文)
目录未来可能会有所调整。
留神,我的系列文章根本不会讲 API,因而须要你有肯定的 TypeScript 应用根底,举荐两个学习材料。
- 深刻了解 TypeScript
- TypeScript 官网文档
联合这两个材料和我的系列教程,把握 TypeScript 不可企及。
接下来,咱们通过几个方面来从宏观的角度来看一下 TypeScript。
<!– more –>
前言
本文波及的题目一共十六道,全副都能够在 typescript-exercises 上在线提交。
能够和标准答案进行比照。
并且因为应用了浏览器缓存,因而无需登录的状况下也能够保障关掉页面,你的答题进度也会保留。
想重置进度,清空缓存,无痕模式或者换浏览器都能够。
题目中波及到的知识点我根本也都在之前的文章中提到了,如果你没有看过,强烈建议先实现后面的教程,而后将下面的题目本人做一遍之后再看本文。
为了不让文章太过于简短,本篇文章分两次公布,一次 8 道题,一共十六道。每道题都有思路,前置常识以及代码。
题目一
题目形容
Intro:
We are starting a small community of users. For performance
reasons we have decided to store all users right in the code.
This way we can provide our developers with more
user-interaction opportunities. With user-related data, at least.
All the GDPR-related issues we will solved some other day.
This would be the base for our future experiments during
these exercises.
Exercise:
Given the data, define the interface "User" and use it accordingly.
题目的大略意思是让你定义一个类型 User
,使得代码能够失常运行。
题目内置代码
export type User = unknown;
export const users: unknown[] = [
{
name: "Max Mustermann",
age: 25,
occupation: "Chimney sweep",
},
{
name: "Kate Müller",
age: 23,
occupation: "Astronaut",
},
];
export function logPerson(user: unknown) {console.log(` - ${user.name}, ${user.age}`);
}
console.log("Users:");
users.forEach(logPerson);
前置常识
- interface 或 type 申明自定义类型
思路
这道题比较简单,咱们只有定义一个 User 类即可。从 users 数组中不难看出,User 中有三个属性 name,age 和 occupation,类型别离为 string,number 和 string。因而间接应用 type 或者 interface 定义自定义类型即可。
代码
外围代码:
export type User = {
name: string;
age: number;
occupation: string;
};
题目二
题目形容
Intro:
All 2 users liked the idea of the community. We should go
forward and introduce some order. We are in Germany after all.
Let's add a couple of admins.
Initially we only had users in the in-memory database. After
introducing Admins, we need to fix the types so that
everything works well together.
Exercise:
Type "Person" is missing, please define it and use
it in persons array and logPerson function in order to fix
all the TS errors.
题目粗心是补充 Person 类,使得代码不报错。
题目内置代码
interface User {
name: string;
age: number;
occupation: string;
}
interface Admin {
name: string;
age: number;
role: string;
}
export type Person = unknown;
export const persons: User[] /* <- Person[] */ = [
{
name: "Max Mustermann",
age: 25,
occupation: "Chimney sweep",
},
{
name: "Jane Doe",
age: 32,
role: "Administrator",
},
{
name: "Kate Müller",
age: 23,
occupation: "Astronaut",
},
{
name: "Bruce Willis",
age: 64,
role: "World saver",
},
];
export function logPerson(user: User) {console.log(` - ${user.name}, ${user.age}`);
}
persons.forEach(logPerson);
前置常识
- 联结类型
思路
咱们间接从报错动手。
不难发现 persons 数组既有 User 又有 Admin。因而 person 的函数签名应该是两者的联结类型。而题目又让咱们补充 Person,于是代码将 Person 定义为 Admin 和 User 的联结类型就不难想到。
代码
外围代码:
export type Person = User | Admin;
这个时候,persons 数组应用的过程只能用 User 和 Admin 的共有属性,也就是 name 和 age,这点前面的题目也会提到。因而如果你应用了 role 或者 occupation 就会报错。怎么解决呢?咱们持续看下一题。
第三题
题目形容
Intro:
Since we already have some of the additional
information about our users, it's a good idea
to output it in a nice way.
Exercise:
Fix type errors in logPerson function.
logPerson function should accept both User and Admin
and should output relevant information according to
the input: occupation for User and role for Admin.
题目内置代码
interface User {
name: string;
age: number;
occupation: string;
}
interface Admin {
name: string;
age: number;
role: string;
}
export type Person = User | Admin;
export const persons: Person[] = [
{
name: "Max Mustermann",
age: 25,
occupation: "Chimney sweep",
},
{
name: "Jane Doe",
age: 32,
role: "Administrator",
},
{
name: "Kate Müller",
age: 23,
occupation: "Astronaut",
},
{
name: "Bruce Willis",
age: 64,
role: "World saver",
},
];
export function logPerson(person: Person) {
let additionalInformation: string;
if (person.role) {additionalInformation = person.role;} else {additionalInformation = person.occupation;}
console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`);
}
persons.forEach(logPerson);
前置常识
- 类型断言
- 类型收敛
- in 操作符
思路
对于类型收敛,我在 TypeScript 类型零碎 做了很详情的探讨。
下面代码报错的起因后面曾经讲过了,那么如何解决呢?因为 person 可能是 User,也可能是 Admin 类型,而 TypeScript 没有足够的信息确定具体是哪一种。因而你应用 User 或者 Admin 特有
的属性就会报错了。
因而解决方案的根本思维就是通知 TypeScript person 以后是 Admin 还是 User 类型。有多种形式能够解决这个问题。
- 将 person 断言为精确的类型。就是通知 TypeScript”交给我吧,person 就是 xxx 类型,有错就我的锅“。
代码:
if ((<Admin>person).role) {additionalInformation = (<Admin>person).role;
} else {additionalInformation = (<User>person).occupation;
}
- 另外一种形式是应用类型膨胀,比方 is,in,typeof,instanceof 等。使得 Typescript 可能 Get 到以后的类型。”哦,person 上有 role 属性啊,那它就是 Admin 类型,有问题我 Typescript 的锅“
这里咱们应用 in 操作符,写起来也很简略。
举荐哪种不必我多说了吧 ?
代码
if ("role" in person) {
// person 会被主动推导为 Admin
additionalInformation = person.role;
} else {
// Person 会被主动推导为 User
additionalInformation = person.occupation;
}
第四题
题目形容
Intro:
As we introduced "type" to both User and Admin
it's now easier to distinguish between them.
Once object type checking logic was extracted
into separate functions isUser and isAdmin -
logPerson function got new type errors.
Exercise:
Figure out how to help TypeScript understand types in
this situation and apply necessary fixes.
大略意思还是让你改代码,使得 Typescript 能了解(不报错)。
题目内置代码
interface User {
type: "user";
name: string;
age: number;
occupation: string;
}
interface Admin {
type: "admin";
name: string;
age: number;
role: string;
}
export type Person = User | Admin;
export const persons: Person[] = [
{
type: "user",
name: "Max Mustermann",
age: 25,
occupation: "Chimney sweep",
},
{type: "admin", name: "Jane Doe", age: 32, role: "Administrator"},
{type: "user", name: "Kate Müller", age: 23, occupation: "Astronaut"},
{type: "admin", name: "Bruce Willis", age: 64, role: "World saver"},
];
export function isAdmin(person: Person) {return person.type === "admin";}
export function isUser(person: Person) {return person.type === "user";}
export function logPerson(person: Person) {
let additionalInformation: string = "";
if (isAdmin(person)) {additionalInformation = person.role;}
if (isUser(person)) {additionalInformation = person.occupation;}
console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`);
}
console.log("Admins:");
persons.filter(isAdmin).forEach(logPerson);
console.log();
console.log("Users:");
persons.filter(isUser).forEach(logPerson);
前置常识
- 类型收敛
- is 操作符
思路
咱们依然从报错动手。
实际上还是 person 的类型问题,没有被膨胀到正确的类型。看题目的代码,冀望成果应该是 如果进入 isAdmin 外部,那么 person 就是 Admin 类型,同理进入 isUser 外部,那么 person 就是 User 类型。
持续看下 isAdmin 和 isUser 的实现:
export function isAdmin(person: Person) {return person.type === "admin";}
export function isUser(person: Person) {return person.type === "user";}
这里咱们冀望的成果是如果 isAdmin 函数返回 true,那么 person 就应该被收敛为 Admin,isUser 同理。
这里就须要用到 is 操作符。
上文提到了类型收敛常见的操作符是 is,in,typeof,instanceof
代码
export function isAdmin(person: Person): person is Admin {return person.type === "admin";}
export function isUser(person: Person): person is User {return person.type === "user";}
这样当 isAdmin 返回 true,那么 person 变量就会被推导成 Admin 类型,而不是联结类型,也就是类型产生了膨胀。
不难看出,这样的类型断言会间接影响到调用 isAdmin 或 isUser 的 函数的入参的类型。
第五题
题目形容
Intro:
Time to filter the data! In order to be flexible
we filter users using a number of criteria and
return only those matching all of the criteria.
We don't need Admins yet, we only filter Users.
Exercise:
Without duplicating type structures, modify
filterUsers function definition so that we can
pass only those criteria which are needed,
and not the whole User information as it is
required now according to typing.
Higher difficulty bonus exercise:
Exclude "type" from filter criterias.
大略意思是让你改 filterUsers,但要留神 DRY
(Don’t Repeat Yourself)。
题目内置代码
interface User {
type: "user";
name: string;
age: number;
occupation: string;
}
interface Admin {
type: "admin";
name: string;
age: number;
role: string;
}
export type Person = User | Admin;
export const persons: Person[] = [
{
type: "user",
name: "Max Mustermann",
age: 25,
occupation: "Chimney sweep",
},
{
type: "admin",
name: "Jane Doe",
age: 32,
role: "Administrator",
},
{
type: "user",
name: "Kate Müller",
age: 23,
occupation: "Astronaut",
},
{
type: "admin",
name: "Bruce Willis",
age: 64,
role: "World saver",
},
{
type: "user",
name: "Wilson",
age: 23,
occupation: "Ball",
},
{
type: "admin",
name: "Agent Smith",
age: 23,
role: "Administrator",
},
];
export const isAdmin = (person: Person): person is Admin =>
person.type === "admin";
export const isUser = (person: Person): person is User =>
person.type === "user";
export function logPerson(person: Person) {
let additionalInformation = "";
if (isAdmin(person)) {additionalInformation = person.role;}
if (isUser(person)) {additionalInformation = person.occupation;}
console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`);
}
export function filterUsers(persons: Person[], criteria: User): User[] {return persons.filter(isUser).filter((user) => {const criteriaKeys = Object.keys(criteria) as (keyof User)[];
return criteriaKeys.every((fieldName) => {return user[fieldName] === criteria[fieldName];
});
});
}
console.log("Users of age 23:");
filterUsers(persons, {age: 23,}).forEach(logPerson);
前置常识
- 泛型
- Partial 泛型
思路
老规矩,从报错动手。
大略意思是 {age: 23} 不残缺,缺失了局部 key。而题目实际上的想法应该是想依据局部内容对人员进行检错。比方能够依据 age 查,也能够依据 name 查,也能够同时依据 age 和 name 查等,这和咱们平时的搜寻逻辑是统一的。
间接用 Partial 泛型即可解决,不懂的能够看下我的文章你不晓得的 TypeScript 泛型(万字长文,倡议珍藏)。
代码
export function filterUsers(persons: Person[], criteria: Partial<User>): User[] {...}
第六题
题目形容
Intro:
Filtering requirements have grown. We need to be
able to filter any kind of Persons.
Exercise:
Fix typing for the filterPersons so that it can filter users
and return User[] when personType='user' and return Admin[]
when personType='admin'. Also filterPersons should accept
partial User/Admin type according to the personType.
`criteria` argument should behave according to the
`personType` argument value. `type` field is not allowed in
the `criteria` field.
Higher difficulty bonus exercise:
Implement a function `getObjectKeys()` which returns more
convenient result for any argument given, so that you don't
need to cast it.
let criteriaKeys = Object.keys(criteria) as (keyof User)[];
-->
let criteriaKeys = getObjectKeys(criteria);
大略意思是让你改 filterUsers,但要留神 DRY
(Don’t Repeat Yourself)。并且能够依据 personType 的不同,返回不同的类型。
题目内置代码
interface User {
type: "user";
name: string;
age: number;
occupation: string;
}
interface Admin {
type: "admin";
name: string;
age: number;
role: string;
}
export type Person = User | Admin;
export const persons: Person[] = [
{
type: "user",
name: "Max Mustermann",
age: 25,
occupation: "Chimney sweep",
},
{type: "admin", name: "Jane Doe", age: 32, role: "Administrator"},
{type: "user", name: "Kate Müller", age: 23, occupation: "Astronaut"},
{type: "admin", name: "Bruce Willis", age: 64, role: "World saver"},
{type: "user", name: "Wilson", age: 23, occupation: "Ball"},
{type: "admin", name: "Agent Smith", age: 23, role: "Anti-virus engineer"},
];
export function logPerson(person: Person) {
console.log(` - ${person.name}, ${person.age}, ${person.type === "admin" ? person.role : person.occupation}`
);
}
export function filterPersons(persons: Person[],
personType: "admin",
criteria: Partial<Person>
): Admin[];
export function filterPersons(persons: Person[],
personType: "user",
criteria: Partial<Person>
): User[];
export function filterPersons(persons: Person[],
personType: string,
criteria: Partial<Person>
): Person[] {
return persons
.filter((person) => person.type === personType)
.filter((person) => {let criteriaKeys = Object.keys(criteria) as (keyof Person)[];
return criteriaKeys.every((fieldName) => {return person[fieldName] === criteria[fieldName];
});
});
}
export const usersOfAge23 = filterPersons(persons, "user", { age: 23});
export const adminsOfAge23 = filterPersons(persons, "admin", { age: 23});
console.log("Users of age 23:");
usersOfAge23.forEach(logPerson);
console.log();
console.log("Admins of age 23:");
adminsOfAge23.forEach(logPerson);
前置常识
- 泛型
- Partial 泛型
- 函数重载
思路
题目形容也懒得看了,间接看报错。
报错信息提醒咱们没有找到适合的函数重载。因而我的思路就是补上适合的重载即可。对于函数重载,我的系列教程不波及,大家能够看下官网材料。
重载之后,不同的状况调用返回值就能够对应不同的类型。本题中就是:
- 如果 personType 是 admin,就会返回 Admin 数组。
- 如果 personType 是 user,就会返回 User 数组。
- 如果 personType 是其余 string,就会返回 Person 数组。
代码
export function filterPersons(persons: Person[], personType: 'admin', criteria: Partial<Person>): Admin[]
export function filterPersons(persons: Person[], personType: 'user', criteria: Partial<Person>): User[]
export function filterPersons(persons: Person[], personType: string, criteria: Partial<Person>): Person[] {...}
第七题
题目形容
Intro:
Filtering was completely removed from the project.
It turned out that this feature was just not needed
for the end-user and we spent a lot of time just because
our office manager told us to do so. Next time we should
instead listen to the product management.
Anyway we have a new plan. CEO's friend Nick told us
that if we randomly swap user names from time to time
in the community, it would be very funny and the project
would definitely succeed!
Exercise:
Implement swap which receives 2 persons and returns them in
the reverse order. The function itself is already
there, actually. We just need to provide it with proper types.
Also this function shouldn't necessarily be limited to just
Person types, lets type it so that it works with any two types
specified.
题目大略意思是让你批改 swap 函数,使得不报错。并且,我心愿这个函数能够实用于任意两个变量,不论其类型一样不一样,也不论二者类型是什么。
题目内置代码
interface User {
type: "user";
name: string;
age: number;
occupation: string;
}
interface Admin {
type: "admin";
name: string;
age: number;
role: string;
}
function logUser(user: User) {const pos = users.indexOf(user) + 1;
console.log(` - #${pos} User: ${user.name}, ${user.age}, ${user.occupation}`);
}
function logAdmin(admin: Admin) {const pos = admins.indexOf(admin) + 1;
console.log(` - #${pos} Admin: ${admin.name}, ${admin.age}, ${admin.role}`);
}
const admins: Admin[] = [
{
type: "admin",
name: "Will Bruces",
age: 30,
role: "Overseer",
},
{
type: "admin",
name: "Steve",
age: 40,
role: "Steve",
},
];
const users: User[] = [
{
type: "user",
name: "Moses",
age: 70,
occupation: "Desert guide",
},
{
type: "user",
name: "Superman",
age: 28,
occupation: "Ordinary person",
},
];
export function swap(v1, v2) {return [v2, v1];
}
function test1() {console.log("test1:");
const [secondUser, firstAdmin] = swap(admins[0], users[1]);
logUser(secondUser);
logAdmin(firstAdmin);
}
function test2() {console.log("test2:");
const [secondAdmin, firstUser] = swap(users[0], admins[1]);
logAdmin(secondAdmin);
logUser(firstUser);
}
function test3() {console.log("test3:");
const [secondUser, firstUser] = swap(users[0], users[1]);
logUser(secondUser);
logUser(firstUser);
}
function test4() {console.log("test4:");
const [firstAdmin, secondAdmin] = swap(admins[1], admins[0]);
logAdmin(firstAdmin);
logAdmin(secondAdmin);
}
function test5() {console.log("test5:");
const [stringValue, numericValue] = swap(123, "Hello World");
console.log(` - String: ${stringValue}`);
console.log(` - Numeric: ${numericValue}`);
}
[test1, test2, test3, test4, test5].forEach((test) => test());
前置常识
- 泛型
思路
题目废话很多,间接疏忽看报错。
这个其实我在 你不晓得的 TypeScript 泛型(万字长文,倡议珍藏)里也讲过了,间接看代码。
代码
export function swap<U, T>(v1: T, v2: U): [U, T] {return [v2, v1];
}
第八题
题目形容
Intro:
Project grew and we ended up in a situation with
some users starting to have more influence.
Therefore, we decided to create a new person type
called PowerUser which is supposed to combine
everything User and Admin have.
Exercise:
Define type PowerUser which should have all fields
from both User and Admin (except for type),
and also have type 'powerUser' without duplicating
all the fields in the code.
题目大略意思是定义一个类型 PowerUser,外面蕴含 User 和 Admin 的所有属性,并且有一个字段是固定的 type: ‘powerUser’。
题目内置代码
interface User {
type: "user";
name: string;
age: number;
occupation: string;
}
interface Admin {
type: "admin";
name: string;
age: number;
role: string;
}
type PowerUser = Omit<User & Admin, "type"> & {type: "powerUser"};
export type Person = User | Admin | PowerUser;
export const persons: Person[] = [
{
type: "user",
name: "Max Mustermann",
age: 25,
occupation: "Chimney sweep",
},
{type: "admin", name: "Jane Doe", age: 32, role: "Administrator"},
{type: "user", name: "Kate Müller", age: 23, occupation: "Astronaut"},
{type: "admin", name: "Bruce Willis", age: 64, role: "World saver"},
{
type: "powerUser",
name: "Nikki Stone",
age: 45,
role: "Moderator",
occupation: "Cat groomer",
},
];
function isAdmin(person: Person): person is Admin {return person.type === "admin";}
function isUser(person: Person): person is User {return person.type === "user";}
function isPowerUser(person: Person): person is PowerUser {return person.type === "powerUser";}
export function logPerson(person: Person) {
let additionalInformation: string = "";
if (isAdmin(person)) {additionalInformation = person.role;}
if (isUser(person)) {additionalInformation = person.occupation;}
if (isPowerUser(person)) {additionalInformation = `${person.role}, ${person.occupation}`;
}
console.log(`${person.name}, ${person.age}, ${additionalInformation}`);
}
console.log("Admins:");
persons.filter(isAdmin).forEach(logPerson);
console.log();
console.log("Users:");
persons.filter(isUser).forEach(logPerson);
console.log();
console.log("Power users:");
persons.filter(isPowerUser).forEach(logPerson);
前置常识
- 汇合操作(穿插类型)
- & 操作符
- 泛型
- Omit 泛型
思路
从题目信息不难看出,就是让咱们实现 PowerUser。
有后面的剖析不难得出咱们只须要:
- 合并 User 和 Admin 的属性即可。借助 & 操作符能够实现。即
User & Admin
。 - 减少特有的属性 type: powerUser。首先去掉上一步合并的 type 属性,而后持续和 {type: “powerUser”} 穿插即可。
- 减少 {type: “powerUser”} 之前应用内置泛型 Omit 将本来的 type 删掉即可。
代码
type PowerUser = Omit<User & Admin, "type"> & {type: "powerUser"};
总结
以上就是给大家带来的题目解析。这八道题的考点有,依照我集体了解的重要水平划分为:
- type 和 interface 的基本操作(必须把握)
- 联结类型 和 穿插类型(强烈建议把握)
- 类型断言和类型膨胀(强烈建议把握)
- 泛型和常见内置泛型(强烈建议把握)
- 函数重载(举荐把握)
最初祝福大家辞别 anyscript,成为 TypeScript 魔法师。
关注我
大家也能够关注我的公众号《脑洞前端》获取更多更陈腐的前端硬核文章,带你意识你不晓得的前端。
公众号【力扣加加】
知乎专栏【Lucifer – 知乎】
点关注,不迷路!