您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

拿6个案例讲解TypeScript 知识点

时间:2020-06-11 14:05:26  来源:  作者:

拿6个案例讲解TypeScript 知识点「干货」

 

作者:ssh前端

转发链接:
https://mp.weixin.qq.com/s/ny_yya50PTJZX-IUERSueA

前言

相信这段时间来,对 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 对应题号,看到结果即可证明编译通过,答案正确。

拿6个案例讲解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<Person>,): Admin[]
function filterPersons(  persons: Person[],
  personType: "user",
  criteria: Partial<Person>,): User[]
function filterPersons(  persons: Person[],
  personType: string,
  criteria: Partial<Person>,) {}

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,并且也严格的维持了顺序,太棒啦。

总结

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



Tags:TypeScript   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
作者:子弈转发链接:https://juejin.im/post/6856410900577026061目录从零开始配置 TypeScript 项目的教程(一)从零开始配置 TypeScript 项目的教程(二) 本篇Jest 确保构建单独通...【详细内容】
2020-08-20  Tags: TypeScript  点击:(54)  评论:(0)  加入收藏
相信这段时间来,对 TypeScript 感兴趣的小伙伴们已经把这个神器给系统的学习了一遍了吧。如果计划开始学习但是还没有开始,或者没有找到资料的同学,可以看下我在之前文章中 前端进阶指南[1] 找一下 TypeScript 部分的教...【详细内容】
2020-06-11  Tags: TypeScript  点击:(49)  评论:(0)  加入收藏
▌简易百科推荐
本文分为三个等级自顶向下地分析了glibc中内存分配与回收的过程。本文不过度关注细节,因此只是分别从arena层次、bin层次、chunk层次进行图解,而不涉及有关指针的具体操作。前...【详细内容】
2021-12-28  linux技术栈    Tags:glibc   点击:(3)  评论:(0)  加入收藏
摘 要 (OF作品展示)OF之前介绍了用python实现数据可视化、数据分析及一些小项目,但基本都是后端的知识。想要做一个好看的可视化大屏,我们还要学一些前端的知识(vue),网上有很多比...【详细内容】
2021-12-27  项目与数据管理    Tags:Vue   点击:(2)  评论:(0)  加入收藏
程序是如何被执行的&emsp;&emsp;程序是如何被执行的?许多开发者可能也没法回答这个问题,大多数人更注重的是如何编写程序,却不会太注意编写好的程序是如何被运行,这并不是一个好...【详细内容】
2021-12-23  IT学习日记    Tags:程序   点击:(9)  评论:(0)  加入收藏
阅读收获✔️1. 了解单点登录实现原理✔️2. 掌握快速使用xxl-sso接入单点登录功能一、早期的多系统登录解决方案 单系统登录解决方案的核心是cookie,cookie携带会话id在浏览器...【详细内容】
2021-12-23  程序yuan    Tags:单点登录(   点击:(8)  评论:(0)  加入收藏
下载Eclipse RCP IDE如果你电脑上还没有安装Eclipse,那么请到这里下载对应版本的软件进行安装。具体的安装步骤就不在这赘述了。创建第一个标准Eclipse RCP应用(总共分为六步)1...【详细内容】
2021-12-22  阿福ChrisYuan    Tags:RCP应用   点击:(7)  评论:(0)  加入收藏
今天想简单聊一聊 Token 的 Value Capture,就是币的价值问题。首先说明啊,这个话题包含的内容非常之光,Token 的经济学设计也可以包含诸多问题,所以几乎不可能把这个问题说的清...【详细内容】
2021-12-21  唐少华TSH    Tags:Token   点击:(10)  评论:(0)  加入收藏
实现效果:假如有10条数据,分组展示,默认在当前页面展示4个,点击换一批,从第5个开始继续展示,到最后一组,再重新返回到第一组 data() { return { qList: [], //处理后...【详细内容】
2021-12-17  Mason程    Tags:VUE   点击:(14)  评论:(0)  加入收藏
什么是性能调优?(what) 为什么需要性能调优?(why) 什么时候需要性能调优?(when) 什么地方需要性能调优?(where) 什么时候来进行性能调优?(who) 怎么样进行性能调优?(How) 硬件配...【详细内容】
2021-12-16  软件测试小p    Tags:性能调优   点击:(20)  评论:(0)  加入收藏
Tasker 是一款适用于 Android 设备的高级自动化应用,它可以通过脚本让重复性的操作自动运行,提高效率。 不知道从哪里听说的抖音 app 会导致 OLED 屏幕烧屏。于是就现学现卖,自...【详细内容】
2021-12-15  ITBang    Tags:抖音防烧屏   点击:(25)  评论:(0)  加入收藏
11 月 23 日,Rust Moderation Team(审核团队)在 GitHub 上发布了辞职公告,即刻生效。根据公告,审核团队集体辞职是为了抗议 Rust 核心团队(Core team)在执行社区行为准则和标准上...【详细内容】
2021-12-15  InfoQ    Tags:Rust   点击:(25)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条