TypeScript 期中考试现在开始

Easter79
• 阅读 775

前言

相信这段时间来,对 TypeScript 感兴趣的小伙伴们已经把这个神器给系统的学习了一遍了吧。如果计划开始学习但是还没有开始,或者没有找到资料的同学,可以看下我在之前文章中 前端进阶指南[1] 找一下 TypeScript 部分的教程,自行学习。

本文从最近在 Github 上比较火的仓库 typescript-exercises[2] 入手,它的中文介绍是 「富有挑战性的 TypeScript 练习集」。里面包含了 15 个 TypeScript 的练习题,我会从其中挑选出几个比较有价值的题目,一起来解答一下。

目标

来看下这个仓库的发起者所定下的目标,让每个人都学会以下知识点的实战运用:

  1. Basic typing.

  2. Refining types.

  3. Union types.

  4. Merged types.

  5. Generics.

  6. Type declarations.

  7. Module augmentation.

  8. Advanced type mapping.

真的都是一些非常有难度且实用的知识点,掌握了它们一定会让我们在编写 TypeScript 类型的时候如虎添翼。

挑战

exercise-00

题目

``import chalk from "chalk"

// 这里需要补全
const users: unknown[] = [
  {
    name: "Max Mustermann",
    age: 25,
    occupation: "Chimney sweep",
  },
  {
    name: "Kate Müller",
    age: 23,
    occupation: "Astronaut",
  },
]

// 这里需要补全
function logPerson(user: unknown) {
  console.log( - ${chalk.green(user.name)}, ${user.age})
}

console.log(chalk.yellow("Users:"))
users.forEach(logPerson)
``

解答

第一题只是个热身题,考察对接口类型定义的掌握,直接定义 User 接口即可实现。

``interface User {
  name: string
  age: number
  occupation: string
}

const users: User[] = [
  {
    name: "Max Mustermann",
    age: 25,
    occupation: "Chimney sweep",
  },
  {
    name: "Kate Müller",
    age: 23,
    occupation: "Astronaut",
  },
]

function logPerson(user: User) {
  console.log( - ${chalk.green(user.name)}, ${user.age})
}

console.log(chalk.yellow("Users:"))
users.forEach(logPerson)
``

或者利用类型推导,users 数组会自动推断出类型:

const users = [   {     name: "Max Mustermann",     age: 25,     occupation: "Chimney sweep",   },   {     name: "Kate Müller",     age: 23,     occupation: "Astronaut",   }, ]

在 VSCode 中,鼠标放到 users 变量上即可看到类型被自动推断出来了:

{   name: string   age: number   occupation: string } ;[]

那么利用 typeof 关键字,配合索引查询,我们也可以轻松取得这个类型。这里 number 的意思就是查找出 users 的所有数字下标对应的值的类型集合。

type User = typeof users[number]

这个仓库提供了每道题的答题机制,执行 npm run 0 对应题号,看到结果即可证明编译通过,答案正确。

TypeScript 期中考试现在开始

execsise-01

题目

最初,我们在数据库中只有 User 类型,后来引入了 Admin 类型。把这两个类型组合成 Person 类型以修复错误。

``interface User {
  name: string
  age: number
  occupation: string
}

interface Admin {
  name: string
  age: number
  role: string
}

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",
  },
]

function logPerson(user: User) {
  console.log( - ${chalk.green(user.name)}, ${user.age})
}

persons.forEach(logPerson)
``

解答

本题考查联合类型的使用:

``// 定义联合类型
type Person = User | Admin

const persons: Person[] /* <- 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",
  },
]

function logPerson(user: Person) {
  console.log( - ${chalk.green(user.name)}, ${user.age})
}
``

exercise-02

根据上题中定义出的 Person 类型,现在需要一个方法打印出它的实例:

题目

function logPerson(person: Person) {   let additionalInformation: string   if (person.role) {     // ❌ 报错 Person 类型中不一定有 role 属性     additionalInformation = person.role   } else {     // ❌ 报错 Person 类型中不一定有 occupation 属性     additionalInformation = person.occupation   }   console.log(     ` - ${chalk.green(person.name)}, ${person.age}, ${additionalInformation}`,   ) }

解答

本题考查 TypeScript 中的「类型保护」,TypeScript 的程序流分析使得某些判断代码包裹之下的代码中,类型可以被进一步收缩。

in 操作符:

function logPerson(person: Person) {   let additionalInformation: string   if ("role" in person) {     additionalInformation = person.role   } else {     additionalInformation = person.occupation   }   console.log(     ` - ${chalk.green(person.name)}, ${person.age}, ${additionalInformation}`,   ) }

函数返回值中的 is 推断:

``function isAdmin(user: Person): user is Admin {
  return user.hasOwnProperty("role")
}

function logPerson(person: Person) {
  let additionalInformation: string
  if (isAdmin(person)) {
    additionalInformation = person.role
  } else {
    additionalInformation = person.occupation
  }
  console.log(
     - ${chalk.green(person.name)}, ${person.age}, ${additionalInformation},
  )
}
``

exercise-04

题目

本题定义了一个 filterUsers 方法,用来通过 person 中的某些字段来筛选出用户的子集。

`function filterUsers(persons: Person[], criteria: User): User[] {
  return persons.filter(isUser).filter((user) => {
    let criteriaKeys = Object.keys(criteria) as (keyof User)[]
    return criteriaKeys.every((fieldName) => {
      return user[fieldName] === criteria[fieldName]
    })
  })
}

console.log(chalk.yellow("Users of age 23:"))

filterUsers(
  persons,
  // ❌ 报错,criteria 定义的是精确的 User 类型,少字段了。
  {
    age: 23,
  },
).forEach(logPerson)
`

可以看出,由于 filterUsers 的第二个筛选参数的类型被精确的定义为 User,所以只传入它的一部分字段 age 就会报错。

解答

本题考查 [mapped-types](https://www.typescriptlang.org/docs/handbook/advanced-types.html#mapped-types "mapped-types") 映射类型,

`type Criteria = {
  [K in keyof User]?: User[K]
}

function filterUsers(persons: Person[], criteria: Criteria): User[] {
  return persons.filter(isUser).filter((user) => {
    let criteriaKeys = Object.keys(criteria) as (keyof User)[]
    return criteriaKeys.every((fieldName) => {
      return user[fieldName] === criteria[fieldName]
    })
  })
}
`

Criteria 利用了映射类型,把 User 的 key 值遍历了一遍,并且加上了 ? 标志代表字段都是可选的,即可完成任务。

也可以利用内置类型 Partial,这个类型用于把另一个类型的字段全部转为可选。

function filterUsers(persons: Person[], criteria: Partial<User>): User[] {}

exercise-05

题目

`function filterPersons( persons: Person[],
  personType: string,
  criteria: unknown,): unknown[] {}

let usersOfAge23: User[] = filterPersons(persons, "user", { age: 23 })
let adminsOfAge23: Admin[] = filterPersons(persons, "admin", { age: 23 })
`

解答

本题返回值类型即可以是 User,也可以是Admin,并且很明显这个结果是由第二个参数是 'user' 还是 'admin' 所决定。

利用函数重载的功能,可以轻松实现,针对每种不同的 personType 参数类型,我们给出不同的返回值类型:

`function filterPersons( persons: Person[],
  personType: "admin",
  criteria: Partial,): Admin[]
function filterPersons( persons: Person[],
  personType: "user",
  criteria: Partial,): User[]
function filterPersons( persons: Person[],
  personType: string,
  criteria: Partial,) {}

let usersOfAge23: User[] = filterPersons(persons, "user", { age: 23 })
let adminsOfAge23: Admin[] = filterPersons(persons, "admin", { age: 23 })
`

exercise-06

题目

`function swap(v1, v2) {
  return [v2, v1]
}

function test1() {
  console.log(chalk.yellow("test1:"))
  const [secondUser, firstAdmin] = swap(admins[0], users[1])
  logUser(secondUser)
  logAdmin(firstAdmin)
}

function test2() {
  console.log(chalk.yellow("test2:"))
  const [secondAdmin, firstUser] = swap(users[0], admins[1])
  logAdmin(secondAdmin)
  logUser(firstUser)
}
`

解答

本题的关键点是 swap 这个方法,它即可以接受Admin类型为参数,也可以接受 User 类型为参数,并且还需要根据传入参数的顺序把它们倒过来放在数组中放回。

也就是说传入的是 v1: User, v2: Admin,需要返回 [Admin, User] 类型。

这题就比较有难度了,首先需要用到泛型[3] 来推断出参数类型,并且和结果关联起来:

function swap<T, K>(v1: T, v2: K) {   return [v2, v1] }

此时结果没有按照我们预期的被推断成 [K, T],而是被推断成了 (K | T)[],这是不符合要求的。

这是因为 TypeScript 默认我们数组中的元素是可变的,所以它会「悲观的」推断我们可能会改变元素的顺序。鼠标放到运行函数时的swap上,我们可以看出类型被推断为了:

function swap<Admin, User>(v1: Admin, v2: User): (Admin | User)[]

要改变这一行为,我们加上 as const 来声明它为常量,严格保证顺序。

function swap<T, K>(v1: T, v2: K) {   return [v2, v1] as const }

此时再看看 swap的推断:

function swap<Admin, User>(v1: Admin, v2: User): readonly [User, Admin]

类型被自动加上了 readonly,并且也严格的维持了顺序,太棒啦。

总结

先用其中的几道题练练手,到第六题的时候,已经涉及到了一些进阶的用法,非常有挑战性。不知道小伙伴们对于这样的做题形式是否感兴趣,还剩下不少有挑战性的题目,如果反馈不错的话,我会继续更新这个系列。

本文仓库地址:

https://github.com/sl1673495/typescript-exercises

❤️ 感谢大家

1.如果本文对你有帮助,就点个赞支持下吧,你的「赞」是我创作的动力。

2.关注公众号「前端从进阶到入院」即可加我好友,我拉你进「前端进阶交流群」,大家一起共同交流和进步。

TypeScript 期中考试现在开始

参考资料

[1]

前端进阶指南: https://github.com/sl1673495/blogs/issues/37

[2]

typescript-exercises: https://github.com/mdevils/typescript-exercises

[3]

泛型: https://www.typescriptlang.org/docs/handbook/generics.html

本文分享自微信公众号 - 前端从进阶到入院(code_with_love)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
PPDB:今晚老齐直播
【今晚老齐直播】今晚(本周三晚)20:0021:00小白开始“用”飞桨(https://www.oschina.net/action/visit/ad?id1185)由PPDE(飞桨(https://www.oschina.net/action/visit/ad?id1185)开发者专家计划)成员老齐,为深度学习小白指点迷津。
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Python之time模块的时间戳、时间字符串格式化与转换
Python处理时间和时间戳的内置模块就有time,和datetime两个,本文先说time模块。关于时间戳的几个概念时间戳,根据1970年1月1日00:00:00开始按秒计算的偏移量。时间元组(struct_time),包含9个元素。 time.struct_time(tm_y
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k