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 免费获取,小白勿进哦!

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

  • 相关阅读:
    我爱Java系列之---【SpringBoot打成war包部署】
    279. Perfect Squares
    矩阵dfs--走回路
    112. Path Sum
    542. 01 Matrix
    106. Construct Binary Tree from Inorder and Postorder Traversal
    105. Construct Binary Tree from Preorder and Inorder Traversal
    Invert Binary Tree
    563 Binary Tree Tilt
    145 Binary Tree Postorder Traversal
  • 原文地址:https://www.cnblogs.com/chengxuyuanaa/p/13068661.html
Copyright © 2011-2022 走看看