TypeScript 期中考試開始了!

前言

相信這段時間來,對 TypeScript 感興趣的小夥伴們已經把這個神器給系統的學習了一遍了吧。若是計劃開始學習可是尚未開始,或者沒有找到資料的同窗,能夠看下我在以前文章中 前端進階指南 找一下 TypeScript 部分的教程,自行學習。html

本文從最近在 Github 上比較火的倉庫 typescript-exercises 入手,它的中文介紹是 「富有挑戰性的 TypeScript 練習集」。裏面包含了 15 個 TypeScript 的練習題,我會從其中挑選出幾個比較有價值的題目,一塊兒來解答一下。前端

目標

來看下這個倉庫的發起者所定下的目標,讓每一個人都學會如下知識點的實戰運用:git

  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 類型的時候如虎添翼。github

挑戰

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 接口便可實現。typescript

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 變量上便可看到類型被自動推斷出來了:npm

{
  name: string
  age: number
  occupation: string
}
;[]
複製代碼

那麼利用 typeof 關鍵字,配合索引查詢,咱們也能夠輕鬆取得這個類型。這裏 number 的意思就是查找出 users 的全部數字下標對應的值的類型集合。數組

type User = typeof users[number]
複製代碼

這個倉庫提供了每道題的答題機制,執行 npm run 0 對應題號,看到結果便可證實編譯經過,答案正確。markdown

execsise-01

題目

最初,咱們在數據庫中只有 User 類型,後來引入了 Admin 類型。把這兩個類型組合成 Person 類型以修復錯誤。app

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 推斷,這個語法是 TypeScript 的 「Type Predicates」類型謂詞:

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,而且也嚴格的維持了順序,太棒啦。

總結

先用其中的幾道題練練手,到第六題的時候,已經涉及到了一些進階的用法,很是有挑戰性。不知道小夥伴們對於這樣的作題形式是否感興趣,還剩下很多有挑戰性的題目,若是反饋不錯的話,我會繼續更新這個系列。

本文倉庫地址:

github.com/sl1673495/t…

❤️ 感謝你們

1.若是本文對你有幫助,就點個贊支持下吧,你的「贊」是我創做的動力。

2.關注公衆號「前端從進階到入院」便可加我好友,我拉你進「前端進階交流羣」,你們一塊兒共同交流和進步。

相關文章
相關標籤/搜索