zoukankan      html  css  js  c++  java
  • TypeScript考试题来了,60%的人不会(附答案)

    前言

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

    本文从最近在 Github 上比较火的仓库 typescript-exercises 入手,它的中文介绍是 「富有挑战性的 TypeScript 练习集」。里面包含了 15 个 TypeScript 的练习题,我会从其中挑选出几个比较有价值的题目,一起来解答一下。
    2.光理论是不够的。在此赠送2020最新企业级 Vue3.0/Js/ES6/TS/React/node等实战视频教程,想学的可进裙 519293536 免费获取,小白勿进哦!

    目标

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

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

    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) 映射类型,

    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] 类型。

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

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

    总结

    1.先用其中的几道题练练手,到第六题的时候,已经涉及到了一些进阶的用法,非常有挑战性。不知道小伙伴们对于这样的做题形式是否感兴趣,还剩下不少有挑战性的题目,如果反馈不错的话,我会继续更新这个系列。
    2.光理论是不够的。在此赠送2020最新企业级 Vue3.0/Js/ES6/TS/React/node等实战视频教程,想学的可进裙 519293536 免费获取,小白勿进哦!

    本文的文字及图片来源于网络加上自己的想法,仅供学习、交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理

  • 相关阅读:
    设置与获取Cookie
    事件对象详解
    兼容各浏览器的鼠标滚轮事件
    正则对象与正则表达式的基础学习
    Ajax 学习
    禅道使用流程概述
    Fiddler、Maven介绍
    Locust安装教程与使用
    常用工具软件包下载地址
    SVN合并步骤
  • 原文地址:https://www.cnblogs.com/chengxuyuanaa/p/13068661.html
Copyright © 2011-2022 走看看